2017-04-19 16:46:28 -04:00
# encoding: utf-8
2019-04-30 10:27:42 +10:00
# frozen_string_literal: true
2017-04-19 16:46:28 -04:00
require 'rails_helper'
describe ThemeField do
2018-03-05 03:04:23 +03:00
after ( :all ) do
ThemeField . destroy_all
end
2020-05-06 22:57:14 +02:00
before do
I18n . locale = :en
end
2018-08-08 07:46:34 +03:00
describe "scope: find_by_theme_ids" do
it "returns result in the specified order" do
theme = Fabricate ( :theme )
theme2 = Fabricate ( :theme )
theme3 = Fabricate ( :theme )
( 0 .. 1 ) . each do | num |
ThemeField . create! ( theme : theme , target_id : num , name : "header" , value : "<a>html</a>" )
ThemeField . create! ( theme : theme2 , target_id : num , name : "header" , value : "<a>html</a>" )
ThemeField . create! ( theme : theme3 , target_id : num , name : "header" , value : "<a>html</a>" )
end
expect ( ThemeField . find_by_theme_ids (
[ theme3 . id , theme . id , theme2 . id ]
) . pluck ( :theme_id )) . to eq (
[ theme3 . id , theme3 . id , theme . id , theme . id , theme2 . id , theme2 . id ]
)
end
end
2018-10-15 12:55:23 +08:00
it 'does not insert a script tag when there are no inline script' do
theme_field = ThemeField . create! ( theme_id : 1 , target_id : 0 , name : "body_tag" , value : '<div>new div</div>' )
2019-04-12 11:36:08 +01:00
theme_field . ensure_baked!
2018-10-15 12:55:23 +08:00
expect ( theme_field . value_baked ) . to_not include ( '<script' )
end
2019-11-12 14:30:19 +00:00
it 'adds an error when optimized image links are included' do
theme_field = ThemeField . create! ( theme_id : 1 , target_id : 0 , name : "body_tag" , value : <<~ HTML )
< img src = "http://mysite.invalid/uploads/default/optimized/1X/6d749a141f513f88f167e750e528515002043da1_2_1282x1000.png" />
HTML
theme_field . ensure_baked!
expect ( theme_field . error ) . to include ( I18n . t ( "themes.errors.optimized_link" ))
theme_field = ThemeField . create! ( theme_id : 1 , target_id : 0 , name : "scss" , value : <<~ SCSS )
body {
background : url ( http : //m ysite . invalid / uploads / default / optimized / 1 X / 6 d749a141f513f88f167e750e528515002043da1_2_1282x1000 . png );
}
SCSS
theme_field . ensure_baked!
expect ( theme_field . error ) . to include ( I18n . t ( "themes.errors.optimized_link" ))
theme_field . update ( value : <<~ SCSS )
body {
background : url ( http : //no tdiscourse . invalid / optimized / my_image . png );
}
SCSS
theme_field . ensure_baked!
expect ( theme_field . error ) . to eq ( nil )
end
2018-10-15 12:55:23 +08:00
it 'only extracts inline javascript to an external file' do
2018-10-18 14:17:10 +08:00
html = <<~ HTML
2020-05-06 22:57:14 +02:00
< script type = "text/discourse-plugin" version = "0.8" >
var a = "inline discourse plugin" ;
< /script>
<script type="text/ template " data-template=" custom - template ">
<div>custom script type</div>
</script>
<script>
var b = " inline raw script ";
</script>
<script type=" texT / jAvasCripT ">
var c = " text / javascript ";
</script>
<script type=" application / javascript ">
var d = " application / javascript ";
</script>
<script src=" / external - script . js "></script>
2018-10-18 14:17:10 +08:00
HTML
2018-10-15 12:55:23 +08:00
theme_field = ThemeField.create!(theme_id: 1, target_id: 0, name: " header ", value: html)
2019-04-12 11:36:08 +01:00
theme_field.ensure_baked!
2018-10-15 12:55:23 +08:00
expect(theme_field.value_baked).to include(" < script src = \ " #{ theme_field . javascript_cache . url } \" ></script>" )
expect ( theme_field . value_baked ) . to include ( "external-script.js" )
2018-11-01 16:01:46 -04:00
expect ( theme_field . value_baked ) . to include ( '<script type="text/template"' )
expect ( theme_field . javascript_cache . content ) . to include ( 'a = "inline discourse plugin"' )
expect ( theme_field . javascript_cache . content ) . to include ( 'b = "inline raw script"' )
expect ( theme_field . javascript_cache . content ) . to include ( 'c = "text/javascript"' )
expect ( theme_field . javascript_cache . content ) . to include ( 'd = "application/javascript"' )
end
it 'adds newlines between the extracted javascripts' do
html = <<~ HTML
2020-05-06 22:57:14 +02:00
< script > var a = 10 < /script>
<script>var b = 10</s cript >
2018-11-01 16:01:46 -04:00
HTML
extracted = <<~ JavaScript
2020-05-06 22:57:14 +02:00
var a = 10
var b = 10
2018-11-01 16:01:46 -04:00
JavaScript
theme_field = ThemeField . create! ( theme_id : 1 , target_id : 0 , name : "header" , value : html )
2019-04-12 11:36:08 +01:00
theme_field . ensure_baked!
2019-01-17 11:46:11 +00:00
expect ( theme_field . javascript_cache . content ) . to include ( extracted )
2018-10-15 12:55:23 +08:00
end
it "correctly extracts and generates errors for transpiled js" do
2017-04-19 16:46:28 -04:00
html = <<HTML
< script type = "text/discourse-plugin" version = "0.8" >
badJavaScript (;
< /script>
HTML
2017-08-31 12:06:56 +08:00
2017-05-02 16:01:01 -04:00
field = ThemeField.create!(theme_id: 1, target_id: 0, name: "header", value: html)
2019-04-12 11:36:08 +01:00
field.ensure_baked!
2017-04-19 16:46:28 -04:00
expect(field.error).not_to eq(nil)
2018-10-15 12:55:23 +08:00
expect(field.value_baked).to include("<script src=\" #{ field . javascript_cache . url } \"></s cript > ")
expect(field.javascript_cache.content).to include(" Theme Transpilation Error : ")
2017-08-31 12:06:56 +08:00
field.update!(value: '')
2019-04-12 11:36:08 +01:00
field.ensure_baked!
2017-04-19 16:46:28 -04:00
expect(field.error).to eq(nil)
end
2018-04-03 12:53:00 +03:00
it " allows us to use theme settings in handlebars templates " do
html = <<HTML
<script type='text/x-handlebars' data-template-name='my-template'>
<div class=" testing - div ">{{themeSettings.string_setting}}</div>
</script>
HTML
2019-04-12 11:36:08 +01:00
ThemeField.create!(theme_id: 1, target_id: 3, name: " yaml ", value: " string_setting : \ "test text \\\" 123! \" " ) . ensure_baked!
2018-10-15 12:55:23 +08:00
theme_field = ThemeField . create! ( theme_id : 1 , target_id : 0 , name : "head_tag" , value : html )
2019-04-12 11:36:08 +01:00
theme_field . ensure_baked!
2018-10-15 12:55:23 +08:00
javascript_cache = theme_field . javascript_cache
2018-04-03 12:53:00 +03:00
2018-10-15 12:55:23 +08:00
expect ( theme_field . value_baked ) . to include ( "<script src= \" #{ javascript_cache . url } \" ></script>" )
expect ( javascript_cache . content ) . to include ( "testing-div" )
expect ( javascript_cache . content ) . to include ( "string_setting" )
2019-01-17 11:46:11 +00:00
expect ( javascript_cache . content ) . to include ( "test text \\\" 123!" )
2018-04-03 12:53:00 +03:00
end
2017-04-19 16:46:28 -04:00
it "correctly generates errors for transpiled css" do
css = "body {"
2017-05-02 16:01:01 -04:00
field = ThemeField . create! ( theme_id : 1 , target_id : 0 , name : "scss" , value : css )
2019-04-12 11:36:08 +01:00
field . ensure_baked!
2017-04-19 16:46:28 -04:00
expect ( field . error ) . not_to eq ( nil )
2017-04-20 16:55:09 -04:00
field . value = "body {color: blue};"
2017-04-19 16:46:28 -04:00
field . save!
2019-04-12 11:36:08 +01:00
field . ensure_baked!
2017-04-20 16:55:09 -04:00
2017-04-19 16:46:28 -04:00
expect ( field . error ) . to eq ( nil )
end
2017-05-10 14:43:05 -04:00
2019-04-12 11:36:08 +01:00
it "allows importing scss files" do
theme = Fabricate ( :theme )
main_field = theme . set_field ( target : :common , name : :scss , value : ".class1{color: red} \n @import 'rootfile1';" )
theme . set_field ( target : :extra_scss , name : "rootfile1" , value : ".class2{color:green} \n @import 'foldername/subfile1';" )
theme . set_field ( target : :extra_scss , name : "rootfile2" , value : ".class3{color:green} " )
theme . set_field ( target : :extra_scss , name : "foldername/subfile1" , value : ".class4{color:yellow} \n @import 'subfile2';" )
theme . set_field ( target : :extra_scss , name : "foldername/subfile2" , value : ".class5{color:yellow} \n @import '../rootfile2';" )
theme . save!
result = main_field . compile_scss [ 0 ]
expect ( result ) . to include ( ".class1" )
expect ( result ) . to include ( ".class2" )
expect ( result ) . to include ( ".class3" )
expect ( result ) . to include ( ".class4" )
expect ( result ) . to include ( ".class5" )
end
2019-06-03 10:41:00 +01:00
it "correctly handles extra JS fields" do
theme = Fabricate ( :theme )
2020-04-06 17:24:59 +01:00
js_field = theme . set_field ( target : :extra_js , name : "discourse/controllers/discovery.js.es6" , value : "import 'discourse/lib/ajax'; console.log('hello from .js.es6');" )
js_2_field = theme . set_field ( target : :extra_js , name : "discourse/controllers/discovery-2.js" , value : "import 'discourse/lib/ajax'; console.log('hello from .js');" )
2019-06-03 10:41:00 +01:00
hbs_field = theme . set_field ( target : :extra_js , name : "discourse/templates/discovery.hbs" , value : "{{hello-world}}" )
2020-02-11 13:38:12 -06:00
raw_hbs_field = theme . set_field ( target : :extra_js , name : "discourse/templates/discovery.hbr" , value : "{{hello-world}}" )
hbr_field = theme . set_field ( target : :extra_js , name : "discourse/templates/other_discovery.hbr" , value : "{{hello-world}}" )
2019-06-03 10:41:00 +01:00
unknown_field = theme . set_field ( target : :extra_js , name : "discourse/controllers/discovery.blah" , value : "this wont work" )
theme . save!
expected_js = <<~ JS
2020-09-15 09:26:33 +02:00
define ( "discourse/controllers/discovery" , [ "discourse/lib/ajax" ] , function ( _ajax ) {
2019-06-03 10:41:00 +01:00
"use strict" ;
var __theme_name__ = " #{ theme . name } " ;
2020-09-15 09:26:33 +02:00
2019-06-03 10:41:00 +01:00
var settings = Discourse . __container__ . lookup ( "service:theme-settings" ) . getObjectForTheme ( #{theme.id});
2020-09-15 09:26:33 +02:00
2019-06-03 10:41:00 +01:00
var themePrefix = function themePrefix ( key ) {
2020-10-13 15:58:08 +02:00
return "theme_translations. #{ theme . id } ." . concat ( key );
2019-06-03 10:41:00 +01:00
};
2020-09-15 09:26:33 +02:00
2020-04-06 17:24:59 +01:00
console . log ( 'hello from .js.es6' );
2019-06-03 10:41:00 +01:00
});
JS
expect ( js_field . reload . value_baked ) . to eq ( expected_js . strip )
expect ( hbs_field . reload . value_baked ) . to include ( 'Ember.TEMPLATES["discovery"]' )
2020-05-05 12:15:03 -04:00
expect ( raw_hbs_field . reload . value_baked ) . to include ( 'addRawTemplate("discovery"' )
expect ( hbr_field . reload . value_baked ) . to include ( 'addRawTemplate("other_discovery"' )
2019-06-03 10:41:00 +01:00
expect ( unknown_field . reload . value_baked ) . to eq ( "" )
expect ( unknown_field . reload . error ) . to eq ( I18n . t ( "themes.compile_error.unrecognized_extension" , extension : "blah" ))
# All together
expect ( theme . javascript_cache . content ) . to include ( 'Ember.TEMPLATES["discovery"]' )
2020-05-05 12:15:03 -04:00
expect ( theme . javascript_cache . content ) . to include ( 'addRawTemplate("discovery"' )
2019-06-03 10:41:00 +01:00
expect ( theme . javascript_cache . content ) . to include ( 'define("discourse/controllers/discovery"' )
2020-04-06 17:24:59 +01:00
expect ( theme . javascript_cache . content ) . to include ( 'define("discourse/controllers/discovery-2"' )
2019-06-03 10:41:00 +01:00
expect ( theme . javascript_cache . content ) . to include ( "var settings =" )
end
2017-12-19 16:10:44 +01:00
def create_upload_theme_field! ( name )
ThemeField . create! (
theme_id : 1 ,
target_id : 0 ,
value : "" ,
type_id : ThemeField . types [ :theme_upload_var ] ,
name : name ,
2019-04-12 11:36:08 +01:00
) . tap { | tf | tf . ensure_baked! }
2017-12-19 16:10:44 +01:00
end
it "ensures we don't use invalid SCSS variable names" do
expect { create_upload_theme_field! ( "42" ) } . to raise_error ( ActiveRecord :: RecordInvalid )
expect { create_upload_theme_field! ( "a42" ) } . not_to raise_error
end
2018-03-05 03:04:23 +03:00
def get_fixture ( type )
File . read ( " #{ Rails . root } /spec/fixtures/theme_settings/ #{ type } _settings.yaml" )
end
def create_yaml_field ( value )
field = ThemeField . create! ( theme_id : 1 , target_id : Theme . targets [ :settings ] , name : "yaml" , value : value )
2019-04-12 11:36:08 +01:00
field . ensure_baked!
2018-03-05 03:04:23 +03:00
field
end
let ( :key ) { "themes.settings_errors" }
2019-03-08 17:49:06 +03:00
it "forces re-transpilation of theme JS when settings YAML changes" do
theme = Fabricate ( :theme )
settings_field = ThemeField . create! ( theme : theme , target_id : Theme . targets [ :settings ] , name : "yaml" , value : "setting: 5" )
html = <<~ HTML
< script type = "text/discourse-plugin" version = "0.8" >
alert ( settings . setting );
< /script>
HTML
js_field = ThemeField.create!(theme: theme, target_id: ThemeField.types[:html], name: "header", value: html)
old_value_baked = js_field.value_baked
settings_field.update!(value: "setting: 66")
js_field.reload
expect(js_field.value_baked).to eq(nil)
js_field.ensure_baked!
expect(js_field.value_baked).to be_present
expect(js_field.value_baked).not_to eq(old_value_baked)
end
2018-03-05 03:04:23 +03:00
it "generates errors for bad YAML" do
yaml = "invalid_setting 5"
field = create_yaml_field(yaml)
expect(field.error).to eq(I18n.t(" #{ key } .invalid_yaml"))
field.value = "valid_setting: true"
field.save!
2019-04-12 11:36:08 +01:00
field.ensure_baked!
2018-03-05 03:04:23 +03:00
expect(field.error).to eq(nil)
end
it "generates errors when default value's type doesn't match setting type" do
field = create_yaml_field(get_fixture("invalid"))
expect(field.error).to include(I18n.t(" #{ key } .default_not_match_type", name: "no_match_setting"))
end
it "generates errors when no default value is passed" do
field = create_yaml_field(get_fixture("invalid"))
expect(field.error).to include(I18n.t(" #{ key } .default_value_missing", name: "no_default_setting"))
end
it "generates errors when invalid type is passed" do
field = create_yaml_field(get_fixture("invalid"))
expect(field.error).to include(I18n.t(" #{ key } .data_type_not_a_number", name: "invalid_type_setting"))
end
it "generates errors when default value is not within allowed range" do
field = create_yaml_field(get_fixture("invalid"))
expect(field.error).to include(I18n.t(" #{ key } .default_out_range", name: "default_out_of_range"))
expect(field.error).to include(I18n.t(" #{ key } .default_out_range", name: "string_default_out_of_range"))
end
it "works correctly when valid yaml is provided" do
field = create_yaml_field(get_fixture("valid"))
expect(field.error).to be_nil
end
2019-01-17 11:46:11 +00:00
describe "locale fields" do
let!(:theme) { Fabricate(:theme) }
let!(:theme2) { Fabricate(:theme) }
let!(:theme3) { Fabricate(:theme) }
let!(:en1) {
2020-05-06 22:57:14 +02:00
ThemeField.create!(theme: theme, target_id: Theme.targets[:translations], name: "en",
value: { en: { somestring1: "helloworld", group: { key1: "enval1" } } }
2019-01-17 11:46:11 +00:00
.deep_stringify_keys.to_yaml
)
}
let!(:fr1) {
ThemeField.create!(theme: theme, target_id: Theme.targets[:translations], name: "fr",
value: { fr: { somestring1: "bonjourworld", group: { key2: "frval2" } } }
.deep_stringify_keys.to_yaml
)
}
let!(:fr2) { ThemeField.create!(theme: theme2, target_id: Theme.targets[:translations], name: "fr", value: "") }
2020-05-06 22:57:14 +02:00
let!(:en2) { ThemeField.create!(theme: theme2, target_id: Theme.targets[:translations], name: "en", value: "") }
2019-01-17 11:46:11 +00:00
let!(:ca3) { ThemeField.create!(theme: theme3, target_id: Theme.targets[:translations], name: "ca", value: "") }
2020-05-06 22:57:14 +02:00
let!(:en3) { ThemeField.create!(theme: theme3, target_id: Theme.targets[:translations], name: "en", value: "") }
2019-01-17 11:46:11 +00:00
describe "scopes" do
2019-02-26 14:22:02 +00:00
it "filter_locale_fields returns results in the correct order" do
expect(ThemeField.find_by_theme_ids([theme3.id, theme.id, theme2.id])
.filter_locale_fields(
2020-05-06 22:57:14 +02:00
["en", "fr"]
2019-01-17 11:46:11 +00:00
)).to eq([en3, en1, fr1, en2, fr2])
end
it "find_first_locale_fields returns only the first locale for each theme" do
expect(ThemeField.find_first_locale_fields(
2020-05-06 22:57:14 +02:00
[theme3.id, theme.id, theme2.id], ["ca", "en", "fr"]
2019-01-17 11:46:11 +00:00
)).to eq([ca3, en1, en2])
end
end
describe "#raw_translation_data" do
it "errors if the top level key is incorrect" do
fr1.update(value: { wrongkey: { somestring1: "bonjourworld" } }.deep_stringify_keys.to_yaml)
expect { fr1.raw_translation_data }.to raise_error(ThemeTranslationParser::InvalidYaml)
end
it "errors if there are multiple top level keys" do
fr1.update(value: { fr: { somestring1: "bonjourworld" }, otherkey: "hello" }.deep_stringify_keys.to_yaml)
expect { fr1.raw_translation_data }.to raise_error(ThemeTranslationParser::InvalidYaml)
end
it "errors if YAML includes arrays" do
fr1.update(value: { fr: ["val1", "val2"] }.deep_stringify_keys.to_yaml)
expect { fr1.raw_translation_data }.to raise_error(ThemeTranslationParser::InvalidYaml)
end
it "errors if YAML has invalid syntax" do
fr1.update(value: "fr: 'valuewithoutclosequote")
expect { fr1.raw_translation_data }.to raise_error(ThemeTranslationParser::InvalidYaml)
end
end
describe "#translation_data" do
it "loads correctly" do
expect(fr1.translation_data).to eq(
fr: { somestring1: "bonjourworld", group: { key2: "frval2" } },
2020-05-06 22:57:14 +02:00
en: { somestring1: "helloworld", group: { key1: "enval1" } }
2019-01-17 11:46:11 +00:00
)
end
it "raises errors for the current locale" do
fr1.update(value: { wrongkey: "hello" }.deep_stringify_keys.to_yaml)
expect { fr1.translation_data }.to raise_error(ThemeTranslationParser::InvalidYaml)
end
it "doesn't raise errors for the fallback locale" do
en1.update(value: { wrongkey: "hello" }.deep_stringify_keys.to_yaml)
expect(fr1.translation_data).to eq(
fr: { somestring1: "bonjourworld", group: { key2: "frval2" } }
)
end
it "merges any overrides" do
# Overrides in the current locale (so in tests that will be english)
theme.update_translation("group.key1", "overriddentest1")
theme.reload
expect(fr1.translation_data).to eq(
fr: { somestring1: "bonjourworld", group: { key2: "frval2" } },
2020-05-06 22:57:14 +02:00
en: { somestring1: "helloworld", group: { key1: "overriddentest1" } }
2019-01-17 11:46:11 +00:00
)
end
end
describe "javascript cache" do
it "is generated correctly" do
fr1.ensure_baked!
expect(fr1.value_baked).to include("<script src=' #{ fr1 . javascript_cache . url } '></s cript > ")
expect(fr1.javascript_cache.content).to include(" bonjourworld ")
expect(fr1.javascript_cache.content).to include(" helloworld ")
expect(fr1.javascript_cache.content).to include(" enval1 ")
end
end
describe " prefix injection " do
it " injects into JS " do
html = <<~HTML
2020-05-06 22:57:14 +02:00
<script type=" text / discourse - plugin " version=" 0 . 8 ">
var a = " inline discourse plugin ";
</script>
2019-01-17 11:46:11 +00:00
HTML
theme_field = ThemeField.create!(theme_id: theme.id, target_id: 0, name: " head_tag ", value: html)
2019-04-12 11:36:08 +01:00
theme_field.ensure_baked!
2019-01-17 11:46:11 +00:00
javascript_cache = theme_field.javascript_cache
expect(javascript_cache.content).to include(" inline discourse plugin ")
expect(javascript_cache.content).to include(" theme_translations . #{theme.id}.")
end
end
end
2017-04-19 16:46:28 -04:00
end