mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 10:20:58 -06:00
bbddce4d3a
Updates our js transpiler code to use Babel 7.11.6 List of changes in this commit: - Updates plugins, babel plugins all have a new version which doesn't contain -es2015- anymore - Drops [transform-es2015-classes](https://babeljs.io/docs/en/babel-plugin-transform-classes) this plugin shouldn't be needed now that we don't support IE - Drops check-es2015-constants, checking constants is now part of babel and the check-constants plugin is deprecated. As a result the behavior slightly changed, and is now wrapping every const call in a readOnlyError function which would throw if assigned a new value. This explains the modified spec. - Adds [proposal-optional-chaining](https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining) ```javascript const obj = { foo: { bar: { baz: 42, }, }, }; const baz = obj?.foo?.bar?.baz; // 42 ``` - Adds [proposal-json-strings](https://babeljs.io/docs/en/babel-plugin-proposal-json-strings) ```javascript // IN const ex = "before after"; // ^ There's a U+2028 char between 'before' and 'after' // OUT const ex = "before\u2028after"; // ^ There's a U+2028 char between 'before' and 'after' ``` - Adds [proposal-nullish-coalescing-operator](https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator) ```javascript var object = {}; var foo = object.foo ?? "default"; // default ``` - Adds [proposal-logical-assignment-operators](https://babeljs.io/docs/en/babel-plugin-proposal-logical-assignment-operators) ```javascript let a; let b = 2; a ||= b; // 2 ``` - Adds [proposal-numeric-separator](https://babeljs.io/docs/en/babel-plugin-proposal-numeric-separator) ```javascript let budget = 1_000_000_000_000; console.log(budget === 10 ** 12); // true ``` - Adds proposal-object-rest-spread https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread ```javascript let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; console.log(x); // 1 console.log(y); // 2 console.log(z); // { a: 3, b: 4 } ``` - Adds proposal-optional-catch-binding https://babeljs.io/docs/en/babel-plugin-proposal-optional-catch-binding ```javascript try { } catch { } finally { // ensures finally is available in every browsers } ``` - Adds improved regex support for firefox through (transform-dotall-regex](https://babeljs.io/docs/en/next/babel-plugin-transform-dotall-regex.html) and (proposal-unicode-property-regex](https://babeljs.io/docs/en/babel-plugin-proposal-unicode-property-regex) - Drops async/generator stuff, the browser we target should allow to use this (excepts iterable async)
758 lines
26 KiB
Ruby
758 lines
26 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
describe Theme do
|
|
after do
|
|
Theme.clear_cache!
|
|
end
|
|
|
|
before do
|
|
I18n.locale = :en
|
|
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 "changing theme name should re-transpile HTML theme fields" do
|
|
theme.update!(name: "old_name")
|
|
html = <<~HTML
|
|
<script type='text/discourse-plugin' version='0.1'>
|
|
const x = 1;
|
|
</script>
|
|
HTML
|
|
theme.set_field(target: :common, name: "head_tag", value: html)
|
|
theme.save!
|
|
field = theme.theme_fields.where(value: html).first
|
|
old_value = field.value_baked
|
|
|
|
theme.update!(name: "new_name")
|
|
field.reload
|
|
new_value = field.value_baked
|
|
expect(old_value).not_to eq(new_value)
|
|
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]
|
|
end
|
|
|
|
it "transpiles ES6 code" do
|
|
html = <<HTML
|
|
<script type='text/discourse-plugin' version='0.1'>
|
|
const x = 1;
|
|
</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("_registerPluginCode('0.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::Compiler.compile('@import "common/foundation/variables"; @import "theme_variables"; @import "desktop_theme"; ', "theme.scss", theme_id: theme.id)
|
|
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::Compiler.compile('@import "theme_variables"; @import "desktop_theme"; ', "theme.scss", theme_id: theme.id)
|
|
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::Compiler.compile('@import "theme_variables"; @import "desktop_theme"; ', "theme.scss", theme_id: theme.id)
|
|
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::Compiler.compile('@import "theme_variables"; @import "desktop_theme"; ', "theme.scss", theme_id: theme.id)
|
|
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!
|
|
|
|
transpiled = <<~HTML
|
|
(function() {
|
|
if ('Discourse' in window && Discourse.__container__) {
|
|
Discourse.__container__
|
|
.lookup("service:theme-settings")
|
|
.registerSettings(#{theme.id}, {"name":"bob"});
|
|
}
|
|
})();
|
|
(function () {
|
|
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
|
var __theme_name__ = "awesome theme\\\"";
|
|
|
|
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
|
|
|
var themePrefix = function themePrefix(key) {
|
|
return "theme_translations.#{theme.id}.".concat(key);
|
|
};
|
|
|
|
Discourse._registerPluginCode('1.0', function (api) {
|
|
try {
|
|
alert(settings.name);
|
|
|
|
var a = function a() {};
|
|
} catch (err) {
|
|
var rescue = require("discourse/lib/utilities").rescueThemeError;
|
|
|
|
rescue(__theme_name__, err, api);
|
|
}
|
|
});
|
|
}
|
|
})();
|
|
HTML
|
|
|
|
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 eq(transpiled.strip)
|
|
|
|
setting = theme.settings.find { |s| s.name == :name }
|
|
setting.value = 'bill'
|
|
theme.save!
|
|
|
|
transpiled = <<~HTML
|
|
(function() {
|
|
if ('Discourse' in window && Discourse.__container__) {
|
|
Discourse.__container__
|
|
.lookup("service:theme-settings")
|
|
.registerSettings(#{theme.id}, {"name":"bill"});
|
|
}
|
|
})();
|
|
(function () {
|
|
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
|
var __theme_name__ = "awesome theme\\\"";
|
|
|
|
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
|
|
|
var themePrefix = function themePrefix(key) {
|
|
return "theme_translations.#{theme.id}.".concat(key);
|
|
};
|
|
|
|
Discourse._registerPluginCode('1.0', function (api) {
|
|
try {
|
|
alert(settings.name);
|
|
|
|
var a = function a() {};
|
|
} catch (err) {
|
|
var rescue = require("discourse/lib/utilities").rescueThemeError;
|
|
|
|
rescue(__theme_name__, err, api);
|
|
}
|
|
});
|
|
}
|
|
})();
|
|
HTML
|
|
|
|
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 eq(transpiled.strip)
|
|
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 = Fabricate(:upload)
|
|
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 '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 "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
|
|
def stub_const(target, const, value)
|
|
old = target.const_get(const)
|
|
target.send(:remove_const, const)
|
|
target.const_set(const, value)
|
|
yield
|
|
ensure
|
|
target.send(:remove_const, const)
|
|
target.const_set(const, old)
|
|
end
|
|
|
|
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
|
|
end
|