discourse/spec/models/theme_spec.rb
Martin Brennan 6fe78cd542
FIX: Make sure reset-new for tracked is not limited by per_page count (#13395)
When dismissing new topics for the Tracked filter, the dismiss was
limited to 30 topics which is the default per page count for TopicQuery.
This happened even if you specified which topic IDs you were
selectively dismissing. This PR fixes that bug, and also moves
the per_page_count into a DEFAULT_PER_PAGE_COUNT for the TopicQuery
so it can be stubbed in tests.

Also moves the unused stub_const method into the spec helpers
for cases like this; it is much better to handle this in one place
with an ensure. In a follow up PR I will clean up other specs that
do the same thing and make them use stub_const.
2021-06-17 08:20:09 +10:00

893 lines
31 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
describe Theme do
after do
Theme.clear_cache!
end
fab! :user do
Fabricate(:user)
end
let(:guardian) do
Guardian.new(user)
end
let(:theme) { Fabricate(:theme, user: user) }
let(:child) { Fabricate(:theme, user: user, component: true) }
it 'can properly clean up color schemes' do
scheme = ColorScheme.create!(theme_id: theme.id, name: 'test')
scheme2 = ColorScheme.create!(theme_id: theme.id, name: 'test2')
Fabricate(:theme, color_scheme_id: scheme2.id)
theme.destroy!
scheme2.reload
expect(scheme2).not_to eq(nil)
expect(scheme2.theme_id).to eq(nil)
expect(ColorScheme.find_by(id: scheme.id)).to eq(nil)
end
it 'can support child themes' do
child.set_field(target: :common, name: "header", value: "World")
child.set_field(target: :desktop, name: "header", value: "Desktop")
child.set_field(target: :mobile, name: "header", value: "Mobile")
child.save!
expect(Theme.lookup_field(child.id, :desktop, "header")).to eq("World\nDesktop")
expect(Theme.lookup_field(child.id, "mobile", :header)).to eq("World\nMobile")
child.set_field(target: :common, name: "header", value: "Worldie")
child.save!
expect(Theme.lookup_field(child.id, :mobile, :header)).to eq("Worldie\nMobile")
parent = Fabricate(:theme, user: user)
parent.set_field(target: :common, name: "header", value: "Common Parent")
parent.set_field(target: :mobile, name: "header", value: "Mobile Parent")
parent.save!
parent.add_relative_theme!(:child, child)
expect(Theme.lookup_field(parent.id, :mobile, "header")).to eq("Common Parent\nMobile Parent\nWorldie\nMobile")
end
it 'can support parent themes' do
child.add_relative_theme!(:parent, theme)
expect(child.parent_themes).to eq([theme])
end
it "can automatically disable for mismatching version" do
expect(theme.supported?).to eq(true)
theme.create_remote_theme!(remote_url: "", minimum_discourse_version: "99.99.99")
theme.save!
expect(theme.supported?).to eq(false)
expect(Theme.transform_ids([theme.id])).to be_empty
end
xit "#transform_ids works with nil values" do
# Used in safe mode
expect(Theme.transform_ids([nil])).to eq([nil])
end
it '#transform_ids filters out disabled components' do
theme.add_relative_theme!(:child, child)
expect(Theme.transform_ids([theme.id], extend: true)).to eq([theme.id, child.id])
child.update!(enabled: false)
expect(Theme.transform_ids([theme.id], extend: true)).to eq([theme.id])
end
it "doesn't allow multi-level theme components" do
grandchild = Fabricate(:theme, user: user)
grandparent = Fabricate(:theme, user: user)
expect do
child.add_relative_theme!(:child, grandchild)
end.to raise_error(Discourse::InvalidParameters, I18n.t("themes.errors.no_multilevels_components"))
expect do
grandparent.add_relative_theme!(:child, theme)
end.to raise_error(Discourse::InvalidParameters, I18n.t("themes.errors.no_multilevels_components"))
end
it "doesn't allow a child to be user selectable" do
child.update(user_selectable: true)
expect(child.errors.full_messages).to contain_exactly(I18n.t("themes.errors.component_no_user_selectable"))
end
it "doesn't allow a child to be set as the default theme" do
expect do
child.set_default!
end.to raise_error(Discourse::InvalidParameters, I18n.t("themes.errors.component_no_default"))
end
it "doesn't allow a component to have color scheme" do
scheme = ColorScheme.create!(name: "test")
child.update(color_scheme: scheme)
expect(child.errors.full_messages).to contain_exactly(I18n.t("themes.errors.component_no_color_scheme"))
end
it 'should correct bad html in body_tag_baked and head_tag_baked' do
theme.set_field(target: :common, name: "head_tag", value: "<b>I am bold")
theme.save!
expect(Theme.lookup_field(theme.id, :desktop, "head_tag")).to eq("<b>I am bold</b>")
end
it 'should precompile fragments in body and head tags' do
with_template = <<HTML
<script type='text/x-handlebars' name='template'>
{{hello}}
</script>
<script type='text/x-handlebars' data-template-name='raw_template.raw'>
{{hello}}
</script>
HTML
theme.set_field(target: :common, name: "header", value: with_template)
theme.save!
field = theme.theme_fields.find_by(target_id: Theme.targets[:common], name: 'header')
baked = Theme.lookup_field(theme.id, :mobile, "header")
expect(baked).to include(field.javascript_cache.url)
expect(field.javascript_cache.content).to include('HTMLBars')
expect(field.javascript_cache.content).to include('raw-handlebars')
end
it 'can destroy unbaked theme without errors' do
with_template = <<HTML
<script type='text/x-handlebars' name='template'>
{{hello}}
</script>
<script type='text/x-handlebars' data-template-name='raw_template.raw'>
{{hello}}
</script>
HTML
theme.set_field(target: :common, name: "header", value: with_template)
theme.save!
field = theme.theme_fields.find_by(target_id: Theme.targets[:common], name: 'header')
baked = Theme.lookup_field(theme.id, :mobile, "header")
ThemeField.where(id: field.id).update_all(compiler_version: 0) # update_all to avoid callbacks
field.reload.destroy!
end
it 'should create body_tag_baked on demand if needed' do
theme.set_field(target: :common, name: :body_tag, value: "<b>test")
theme.save
ThemeField.update_all(value_baked: nil)
expect(Theme.lookup_field(theme.id, :desktop, :body_tag)).to match(/<b>test<\/b>/)
end
it 'can find fields for multiple themes' do
theme2 = Fabricate(:theme)
theme.set_field(target: :common, name: :body_tag, value: "<b>testtheme1</b>")
theme2.set_field(target: :common, name: :body_tag, value: "<b>theme2test</b>")
theme.save!
theme2.save!
field = Theme.lookup_field([theme.id, theme2.id], :desktop, :body_tag)
expect(field).to match(/<b>testtheme1<\/b>/)
expect(field).to match(/<b>theme2test<\/b>/)
end
describe "#switch_to_component!" do
it "correctly converts a theme to component" do
theme.add_relative_theme!(:child, child)
scheme = ColorScheme.create!(name: 'test')
theme.update!(color_scheme_id: scheme.id, user_selectable: true)
theme.set_default!
theme.switch_to_component!
theme.reload
expect(theme.component).to eq(true)
expect(theme.user_selectable).to eq(false)
expect(theme.default?).to eq(false)
expect(theme.color_scheme_id).to eq(nil)
expect(ChildTheme.where(parent_theme: theme).exists?).to eq(false)
end
end
describe "#switch_to_theme!" do
it "correctly converts a component to theme" do
theme.add_relative_theme!(:child, child)
child.switch_to_theme!
theme.reload
child.reload
expect(child.component).to eq(false)
expect(ChildTheme.where(child_theme: child).exists?).to eq(false)
end
end
describe ".transform_ids" do
let!(:orphan1) { Fabricate(:theme, component: true) }
let!(:child) { Fabricate(:theme, component: true) }
let!(:child2) { Fabricate(:theme, component: true) }
let!(:orphan2) { Fabricate(:theme, component: true) }
let!(:orphan3) { Fabricate(:theme, component: true) }
let!(:orphan4) { Fabricate(:theme, component: true) }
before do
theme.add_relative_theme!(:child, child)
theme.add_relative_theme!(:child, child2)
end
it "returns an empty array if no ids are passed" do
expect(Theme.transform_ids([])).to eq([])
end
it "adds the child themes of the parent" do
sorted = [child.id, child2.id].sort
expect(Theme.transform_ids([theme.id])).to eq([theme.id, *sorted])
expect(Theme.transform_ids([theme.id, orphan1.id, orphan2.id])).to eq([theme.id, orphan1.id, *sorted, orphan2.id])
end
it "doesn't insert children when extend is false" do
fake_id = orphan2.id
fake_id2 = orphan3.id
fake_id3 = orphan4.id
expect(Theme.transform_ids([theme.id], extend: false)).to eq([theme.id])
expect(Theme.transform_ids([theme.id, fake_id3, fake_id, fake_id2, fake_id2], extend: false))
.to eq([theme.id, fake_id, fake_id2, fake_id3])
end
end
context "plugin api" do
def transpile(html)
f = ThemeField.create!(target_id: Theme.targets[:mobile], theme_id: 1, name: "after_header", value: html)
f.ensure_baked!
[f.value_baked, f.javascript_cache, f]
end
it "transpiles ES6 code" do
html = <<HTML
<script type='text/discourse-plugin' version='0.1'>
const x = 1;
</script>
HTML
baked, javascript_cache, field = transpile(html)
expect(baked).to include(javascript_cache.url)
expect(javascript_cache.content).to include("if ('define' in window) {")
expect(javascript_cache.content).to include(
"define(\"discourse/theme-#{field.theme_id}/initializers/theme-field-#{field.id}-mobile-html-script-1\""
)
expect(javascript_cache.content).to include(
"settings = require(\"discourse/lib/theme-settings-store\").getObjectForTheme(#{field.theme_id});"
)
expect(javascript_cache.content).to include("name: \"theme-field-#{field.id}-mobile-html-script-1\",")
expect(javascript_cache.content).to include("after: \"inject-objects\",")
expect(javascript_cache.content).to include("(0, _pluginApi.withPluginApi)(\"0.1\", function (api) {")
expect(javascript_cache.content).to include("var x = 1;")
end
it "wraps constants calls in a readOnlyError function" do
html = <<HTML
<script type='text/discourse-plugin' version='0.1'>
const x = 1;
x = 2;
</script>
HTML
baked, javascript_cache = transpile(html)
expect(baked).to include(javascript_cache.url)
expect(javascript_cache.content).to include('var x = 1;')
expect(javascript_cache.content).to include('x = (_readOnlyError("x"), 2);')
end
end
context 'theme upload vars' do
let :image do
file_from_fixtures("logo.png")
end
it 'can handle uploads based of ThemeField' do
upload = UploadCreator.new(image, "logo.png").create_for(-1)
theme.set_field(target: :common, name: :logo, upload_id: upload.id, type: :theme_upload_var)
theme.set_field(target: :common, name: :scss, value: 'body {background-image: url($logo)}')
theme.save!
# make sure we do not nuke it
freeze_time (SiteSetting.clean_orphan_uploads_grace_period_hours + 1).hours.from_now
Jobs::CleanUpUploads.new.execute(nil)
expect(Upload.where(id: upload.id)).to be_exist
# no error for theme field
theme.reload
expect(theme.theme_fields.find_by(name: :scss).error).to eq(nil)
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
expect(scss).to include(upload.url)
end
end
context "theme settings" do
it "allows values to be used in scss" do
theme.set_field(target: :settings, name: :yaml, value: "background_color: red\nfont_size: 25px")
theme.set_field(target: :common, name: :scss, value: 'body {background-color: $background_color; font-size: $font-size}')
theme.save!
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
expect(scss).to include("background-color:red")
expect(scss).to include("font-size:25px")
setting = theme.settings.find { |s| s.name == :font_size }
setting.value = '30px'
theme.save!
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
expect(scss).to include("font-size:30px")
# Escapes correctly. If not, compiling this would throw an exception
setting.value = <<~MULTILINE
\#{$fakeinterpolatedvariable}
andanothervalue 'withquotes'; margin: 0;
MULTILINE
theme.set_field(target: :common, name: :scss, value: 'body {font-size: quote($font-size)}')
theme.save!
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
expect(scss).to include('font-size:"#{$fakeinterpolatedvariable}\a andanothervalue \'withquotes\'; margin: 0;\a"')
end
it "allows values to be used in JS" do
theme.name = 'awesome theme"'
theme.set_field(target: :settings, name: :yaml, value: "name: bob")
theme_field = theme.set_field(target: :common, name: :after_header, value: '<script type="text/discourse-plugin" version="1.0">alert(settings.name); let a = ()=>{};</script>')
theme.save!
theme_field.reload
expect(Theme.lookup_field(theme.id, :desktop, :after_header)).to include(theme_field.javascript_cache.url)
expect(theme_field.javascript_cache.content).to include("if ('require' in window) {")
expect(theme_field.javascript_cache.content).to include(
"require(\"discourse/lib/theme-settings-store\").registerSettings(#{theme_field.theme.id}, {\"name\":\"bob\"});"
)
expect(theme_field.javascript_cache.content).to include("if ('define' in window) {")
expect(theme_field.javascript_cache.content).to include(
"define(\"discourse/theme-#{theme_field.theme.id}/initializers/theme-field-#{theme_field.id}-common-html-script-1\","
)
expect(theme_field.javascript_cache.content).to include("name: \"theme-field-#{theme_field.id}-common-html-script-1\",")
expect(theme_field.javascript_cache.content).to include("after: \"inject-objects\",")
expect(theme_field.javascript_cache.content).to include("(0, _pluginApi.withPluginApi)(\"1.0\", function (api)")
expect(theme_field.javascript_cache.content).to include("alert(settings.name)")
expect(theme_field.javascript_cache.content).to include("var a = function a() {}")
setting = theme.settings.find { |s| s.name == :name }
setting.value = 'bill'
theme.save!
theme_field.reload
expect(theme_field.javascript_cache.content).to include(
"require(\"discourse/lib/theme-settings-store\").registerSettings(#{theme_field.theme.id}, {\"name\":\"bill\"});"
)
expect(Theme.lookup_field(theme.id, :desktop, :after_header)).to include(theme_field.javascript_cache.url)
end
it 'is empty when the settings are invalid' do
theme.set_field(target: :settings, name: :yaml, value: 'nil_setting: ')
theme.save!
expect(theme.settings).to be_empty
end
end
it 'correctly caches theme ids' do
Theme.destroy_all
theme
theme2 = Fabricate(:theme)
expect(Theme.theme_ids).to contain_exactly(theme.id, theme2.id)
expect(Theme.user_theme_ids).to eq([])
theme.update!(user_selectable: true)
expect(Theme.user_theme_ids).to contain_exactly(theme.id)
theme2.update!(user_selectable: true)
expect(Theme.user_theme_ids).to contain_exactly(theme.id, theme2.id)
theme.update!(user_selectable: false)
theme2.update!(user_selectable: false)
theme.set_default!
expect(Theme.user_theme_ids).to contain_exactly(theme.id)
theme.destroy
theme2.destroy
expect(Theme.theme_ids).to eq([])
expect(Theme.user_theme_ids).to eq([])
end
it 'correctly caches user_themes template' do
Theme.destroy_all
json = Site.json_for(guardian)
user_themes = JSON.parse(json)["user_themes"]
expect(user_themes).to eq([])
theme = Fabricate(:theme, name: "bob", user_selectable: true)
theme.save!
json = Site.json_for(guardian)
user_themes = JSON.parse(json)["user_themes"].map { |t| t["name"] }
expect(user_themes).to eq(["bob"])
theme.name = "sam"
theme.save!
json = Site.json_for(guardian)
user_themes = JSON.parse(json)["user_themes"].map { |t| t["name"] }
expect(user_themes).to eq(["sam"])
Theme.destroy_all
json = Site.json_for(guardian)
user_themes = JSON.parse(json)["user_themes"]
expect(user_themes).to eq([])
end
def cached_settings(id)
Theme.find_by(id: id).included_settings.to_json
end
it 'clears color scheme cache correctly' do
Theme.destroy_all
cs = Fabricate(:color_scheme, name: 'Fancy', color_scheme_colors: [
Fabricate(:color_scheme_color, name: 'header_primary', hex: 'F0F0F0'),
Fabricate(:color_scheme_color, name: 'header_background', hex: '1E1E1E'),
Fabricate(:color_scheme_color, name: 'tertiary', hex: '858585')
])
theme = Fabricate(:theme,
user_selectable: true,
user: Fabricate(:admin),
color_scheme_id: cs.id
)
theme.set_default!
expect(ColorScheme.hex_for_name('header_primary')).to eq('F0F0F0')
Theme.clear_default!
expect(ColorScheme.hex_for_name('header_primary')).to eq('333333')
end
it "correctly notifies about theme changes" do
cs1 = Fabricate(:color_scheme)
cs2 = Fabricate(:color_scheme)
theme = Fabricate(:theme,
user_selectable: true,
user: user,
color_scheme_id: cs1.id
)
messages = MessageBus.track_publish do
theme.save!
end.filter { |m| m.channel == "/file-change" }
expect(messages.count).to eq(1)
expect(messages.first.data.map { |d| d[:target] }).to contain_exactly(:desktop_theme, :mobile_theme)
# With color scheme change:
messages = MessageBus.track_publish do
theme.color_scheme_id = cs2.id
theme.save!
end.filter { |m| m.channel == "/file-change" }
expect(messages.count).to eq(1)
expect(messages.first.data.map { |d| d[:target] }).to contain_exactly(:admin, :desktop, :desktop_theme, :mobile, :mobile_theme)
end
it 'includes theme_uploads in settings' do
Theme.destroy_all
upload = UploadCreator.new(file_from_fixtures("logo.png"), "logo.png").create_for(-1)
theme.set_field(type: :theme_upload_var, target: :common, name: "bob", upload_id: upload.id)
theme.save!
json = JSON.parse(cached_settings(theme.id))
expect(json["theme_uploads"]["bob"]).to eq(upload.url)
end
it 'uses CDN url for theme_uploads in settings' do
set_cdn_url("http://cdn.localhost")
Theme.destroy_all
upload = UploadCreator.new(file_from_fixtures("logo.png"), "logo.png").create_for(-1)
theme.set_field(type: :theme_upload_var, target: :common, name: "bob", upload_id: upload.id)
theme.save!
json = JSON.parse(cached_settings(theme.id))
expect(json["theme_uploads"]["bob"]).to eq("http://cdn.localhost#{upload.url}")
end
it 'uses CDN url for settings of type upload' do
set_cdn_url("http://cdn.localhost")
Theme.destroy_all
upload = UploadCreator.new(file_from_fixtures("logo.png"), "logo.png").create_for(-1)
theme.set_field(target: :settings, name: "yaml", value: <<~YAML)
my_upload:
type: upload
default: ""
YAML
ThemeSetting.create!(theme: theme, data_type: ThemeSetting.types[:upload], value: upload.url, name: "my_upload")
theme.save!
json = JSON.parse(cached_settings(theme.id))
expect(json["my_upload"]).to eq("http://cdn.localhost#{upload.url}")
end
it 'handles settings cache correctly' do
Theme.destroy_all
expect(cached_settings(theme.id)).to eq("{}")
theme.set_field(target: :settings, name: "yaml", value: "boolean_setting: true")
theme.save!
expect(cached_settings(theme.id)).to match(/\"boolean_setting\":true/)
theme.settings.first.value = "false"
theme.save!
expect(cached_settings(theme.id)).to match(/\"boolean_setting\":false/)
child.set_field(target: :settings, name: "yaml", value: "integer_setting: 54")
child.save!
theme.add_relative_theme!(:child, child)
json = cached_settings(theme.id)
expect(json).to match(/\"boolean_setting\":false/)
expect(json).to match(/\"integer_setting\":54/)
expect(cached_settings(child.id)).to eq("{\"integer_setting\":54}")
child.destroy!
json = cached_settings(theme.id)
expect(json).not_to match(/\"integer_setting\":54/)
expect(json).to match(/\"boolean_setting\":false/)
end
describe "convert_settings" do
it 'can migrate a list field to a string field with json schema' do
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"green,globe\"\n type: \"list\"")
theme.save!
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
setting.value = "red,globe|green,cog|brown,users"
theme.save!
expect(setting.type).to eq(ThemeSetting.types[:list])
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/valid_settings.yaml")
theme.set_field(target: :settings, name: "yaml", value: yaml)
theme.save!
theme.convert_settings
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
expect(JSON.parse(setting.value)).to eq(JSON.parse('[{"color":"red","icon":"globe"},{"color":"green","icon":"cog"},{"color":"brown","icon":"users"}]'))
expect(setting.type).to eq(ThemeSetting.types[:string])
end
it 'does not update setting if data does not validate against json schema' do
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"green,globe\"\n type: \"list\"")
theme.save!
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
# json_schema_settings.yaml defines only two properties per object and disallows additionalProperties
setting.value = "red,globe,hey|green,cog,hey|brown,users,nay"
theme.save!
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/valid_settings.yaml")
theme.set_field(target: :settings, name: "yaml", value: yaml)
theme.save!
expect { theme.convert_settings }.to raise_error("Schema validation failed")
setting.value = "red,globe|green,cog|brown"
theme.save!
expect { theme.convert_settings }.not_to raise_error
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
expect(setting.type).to eq(ThemeSetting.types[:string])
end
it 'warns when the theme has modified the setting type but data cannot be converted' do
begin
@orig_logger = Rails.logger
Rails.logger = FakeLogger.new
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"\"\n type: \"list\"")
theme.save!
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
setting.value = "red,globe"
theme.save!
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"\"\n type: \"string\"")
theme.save!
theme.convert_settings
expect(setting.value).to eq("red,globe")
expect(Rails.logger.warnings[0]).to include("Theme setting type has changed but cannot be converted.")
ensure
Rails.logger = @orig_logger
end
end
end
describe "theme translations" do
it "can list working theme_translation_manager objects" do
en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
en:
theme_metadata:
description: "Description of my theme"
group_of_translations:
translation1: en test1
translation2: en test2
base_translation1: en test3
base_translation2: en test4
YAML
fr_translation = ThemeField.create!(theme_id: theme.id, name: "fr", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
fr:
group_of_translations:
translation2: fr test2
base_translation2: fr test4
base_translation3: fr test5
YAML
I18n.locale = :fr
theme.update_translation("group_of_translations.translation1", "overriddentest1")
translations = theme.translations
theme.reload
expect(translations.map(&:key)).to eq([
"group_of_translations.translation1",
"group_of_translations.translation2",
"base_translation1",
"base_translation2",
"base_translation3"
])
expect(translations.map(&:default)).to eq([
"en test1",
"fr test2",
"en test3",
"fr test4",
"fr test5"
])
expect(translations.map(&:value)).to eq([
"overriddentest1",
"fr test2",
"en test3",
"fr test4",
"fr test5"
])
end
it "can list internal theme_translation_manager objects" do
en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
en:
theme_metadata:
description: "Description of my theme"
another_translation: en test4
YAML
translations = theme.internal_translations
expect(translations.map(&:key)).to contain_exactly("theme_metadata.description")
expect(translations.map(&:value)).to contain_exactly("Description of my theme")
end
it "can create a hash of overridden values" do
en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
en:
group_of_translations:
translation1: en test1
YAML
theme.update_translation("group_of_translations.translation1", "overriddentest1")
I18n.locale = :fr
theme.update_translation("group_of_translations.translation1", "overriddentest2")
theme.reload
expect(theme.translation_override_hash).to eq(
"en" => {
"group_of_translations" => {
"translation1" => "overriddentest1"
}
},
"fr" => {
"group_of_translations" => {
"translation1" => "overriddentest2"
}
}
)
end
it "fall back when listing baked field" do
theme2 = Fabricate(:theme)
en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: '')
fr_translation = ThemeField.create!(theme_id: theme.id, name: "fr", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: '')
en_translation2 = ThemeField.create!(theme_id: theme2.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: '')
expect(Theme.list_baked_fields([theme.id, theme2.id], :translations, 'fr').map(&:id)).to contain_exactly(fr_translation.id, en_translation2.id)
end
end
describe "automatic recompile" do
it 'must recompile after bumping theme_field version' do
child.set_field(target: :common, name: "header", value: "World")
child.set_field(target: :extra_js, name: "test.js.es6", value: "const hello = 'world';")
child.save!
first_common_value = Theme.lookup_field(child.id, :desktop, "header")
first_extra_js_value = Theme.lookup_field(child.id, :extra_js, nil)
Theme.stubs(:compiler_version).returns("SOME_NEW_HASH") do
second_common_value = Theme.lookup_field(child.id, :desktop, "header")
second_extra_js_value = Theme.lookup_field(child.id, :extra_js, nil)
new_common_compiler_version = ThemeField.find_by(theme_id: child.id, name: "header").compiler_version
new_extra_js_compiler_version = ThemeField.find_by(theme_id: child.id, name: "test.js.es6").compiler_version
expect(first_common_value).to eq(second_common_value)
expect(first_extra_js_value).to eq(second_extra_js_value)
expect(new_common_compiler_version).to eq("SOME_NEW_HASH")
expect(new_extra_js_compiler_version).to eq("SOME_NEW_HASH")
end
end
it 'recompiles when the hostname changes' do
theme.set_field(target: :settings, name: :yaml, value: "name: bob")
theme_field = theme.set_field(target: :common, name: :after_header, value: '<script>console.log("hello world");</script>')
theme.save!
expect(Theme.lookup_field(theme.id, :common, :after_header)).to include("_ws=#{Discourse.current_hostname}")
SiteSetting.force_hostname = "someotherhostname.com"
Theme.clear_cache!
expect(Theme.lookup_field(theme.id, :common, :after_header)).to include("_ws=someotherhostname.com")
end
end
describe "extra_scss" do
let(:scss) { "body { background: red}" }
let(:second_file_scss) { "p { color: blue};" }
let(:child_scss) { "body { background: green}" }
let(:theme) { Fabricate(:theme).tap { |t|
t.set_field(target: :extra_scss, name: "my_files/magic", value: scss)
t.set_field(target: :extra_scss, name: "my_files/magic2", value: second_file_scss)
t.save!
}}
let(:child_theme) { Fabricate(:theme).tap { |t|
t.component = true
t.set_field(target: :extra_scss, name: "my_files/moremagic", value: child_scss)
t.save!
theme.add_relative_theme!(:child, t)
}}
let(:compiler) {
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
manager.compile(force: true)
}
it "works when importing file by path" do
theme.set_field(target: :common, name: :scss, value: '@import "my_files/magic";')
theme.save!
css, _map = compiler
expect(css).to include("body{background:red}")
end
it "works when importing multiple files" do
theme.set_field(target: :common, name: :scss, value: '@import "my_files/magic"; @import "my_files/magic2"')
theme.save!
css, _map = compiler
expect(css).to include("body{background:red}")
expect(css).to include("p{color:blue}")
end
it "works for child themes" do
child_theme.set_field(target: :common, name: :scss, value: '@import "my_files/moremagic"')
child_theme.save!
manager = Stylesheet::Manager.new(:desktop_theme, child_theme.id)
css, _map = manager.compile(force: true)
expect(css).to include("body{background:green}")
end
end
describe "scss_variables" do
it "is empty by default" do
expect(theme.scss_variables).to eq(nil)
end
it "includes settings and uploads when set" do
theme.set_field(target: :settings, name: :yaml, value: "background_color: red\nfont_size: 25px")
upload = UploadCreator.new(file_from_fixtures("logo.png"), "logo.png").create_for(-1)
theme.set_field(type: :theme_upload_var, target: :common, name: "bobby", upload_id: upload.id)
theme.save!
expect(theme.scss_variables).to include("$background_color: unquote(\"red\")")
expect(theme.scss_variables).to include("$font_size: unquote(\"25px\")")
expect(theme.scss_variables).to include("$bobby: ")
end
end
describe "#baked_js_tests_with_digest" do
before do
ThemeField.create!(
theme_id: theme.id,
target_id: Theme.targets[:settings],
name: "yaml",
value: "some_number: 1"
)
theme.set_field(
target: :tests_js,
type: :js,
name: "acceptance/some-test.js",
value: "assert.ok(true);"
)
theme.save!
end
it 'returns nil for content and digest if theme does not have tests' do
ThemeField.destroy_all
expect(theme.baked_js_tests_with_digest).to eq([nil, nil])
end
it 'digest does not change when settings are changed' do
content, digest = theme.baked_js_tests_with_digest
expect(content).to be_present
expect(digest).to be_present
expect(content).to include("assert.ok(true);")
theme.update_setting(:some_number, 55)
theme.save!
expect(theme.build_settings_hash[:some_number]).to eq(55)
new_content, new_digest = theme.baked_js_tests_with_digest
expect(new_content).to eq(content)
expect(new_digest).to eq(digest)
end
end
end