2019-04-30 08:27:42 +08:00
# frozen_string_literal: true
2017-04-12 22:52:52 +08:00
require 'rails_helper'
describe Theme do
2018-09-13 14:23:32 +08:00
after do
2017-04-12 22:52:52 +08:00
Theme . clear_cache!
end
2019-05-07 11:12:20 +08:00
fab! :user do
2017-04-12 22:52:52 +08:00
Fabricate ( :user )
end
2017-09-04 19:27:58 +08:00
let ( :guardian ) do
Guardian . new ( user )
end
2018-08-08 12:46:34 +08:00
let ( :theme ) { Fabricate ( :theme , user : user ) }
2018-08-24 09:30:00 +08:00
let ( :child ) { Fabricate ( :theme , user : user , component : true ) }
2021-06-15 23:25:06 +08:00
2017-05-05 02:03:07 +08:00
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' )
2018-08-08 12:46:34 +08:00
Fabricate ( :theme , color_scheme_id : scheme2 . id )
2017-05-05 02:03:07 +08:00
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
2017-04-12 22:52:52 +08:00
it 'can support child themes' do
2017-05-03 04:01:01 +08:00
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 " )
2017-04-12 22:52:52 +08:00
child . save!
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( child . id , :desktop , " header " ) ) . to eq ( " World \n Desktop " )
expect ( Theme . lookup_field ( child . id , " mobile " , :header ) ) . to eq ( " World \n Mobile " )
2017-04-12 22:52:52 +08:00
2017-05-03 04:01:01 +08:00
child . set_field ( target : :common , name : " header " , value : " Worldie " )
2017-04-12 22:52:52 +08:00
child . save!
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( child . id , :mobile , :header ) ) . to eq ( " Worldie \n Mobile " )
2017-04-12 22:52:52 +08:00
2018-08-08 12:46:34 +08:00
parent = Fabricate ( :theme , user : user )
2017-04-12 22:52:52 +08:00
2017-05-03 04:01:01 +08:00
parent . set_field ( target : :common , name : " header " , value : " Common Parent " )
parent . set_field ( target : :mobile , name : " header " , value : " Mobile Parent " )
2017-04-12 22:52:52 +08:00
parent . save!
2019-11-28 13:19:01 +08:00
parent . add_relative_theme! ( :child , child )
2017-04-12 22:52:52 +08:00
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( parent . id , :mobile , " header " ) ) . to eq ( " Common Parent \n Mobile Parent \n Worldie \n Mobile " )
2017-04-12 22:52:52 +08:00
end
2019-11-28 13:19:01 +08:00
it 'can support parent themes' do
child . add_relative_theme! ( :parent , theme )
expect ( child . parent_themes ) . to eq ( [ theme ] )
end
2019-01-25 22:19:01 +08:00
it " can automatically disable for mismatching version " do
theme . create_remote_theme! ( remote_url : " " , minimum_discourse_version : " 99.99.99 " )
2019-08-29 22:47:08 +08:00
theme . save!
2018-08-08 12:46:34 +08:00
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ ] )
2018-08-08 12:46:34 +08:00
end
2021-06-15 14:57:17 +08:00
it " # transform_ids works with nil values " do
2019-01-26 01:00:19 +08:00
# Used in safe mode
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( nil ) ) . to eq ( [ ] )
2019-01-26 01:00:19 +08:00
end
2019-07-03 16:18:11 +08:00
it '#transform_ids filters out disabled components' do
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ theme . id , child . id ] )
2019-07-03 16:18:11 +08:00
child . update! ( enabled : false )
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ theme . id ] )
2019-07-03 16:18:11 +08:00
end
2018-08-08 12:46:34 +08:00
it " doesn't allow multi-level theme components " do
grandchild = Fabricate ( :theme , user : user )
grandparent = Fabricate ( :theme , user : user )
2017-04-12 22:52:52 +08:00
2018-08-08 12:46:34 +08:00
expect do
2019-11-28 13:19:01 +08:00
child . add_relative_theme! ( :child , grandchild )
2018-08-08 12:46:34 +08:00
end . to raise_error ( Discourse :: InvalidParameters , I18n . t ( " themes.errors.no_multilevels_components " ) )
expect do
2019-11-28 13:19:01 +08:00
grandparent . add_relative_theme! ( :child , theme )
2018-08-08 12:46:34 +08:00
end . to raise_error ( Discourse :: InvalidParameters , I18n . t ( " themes.errors.no_multilevels_components " ) )
end
2017-04-12 22:52:52 +08:00
2018-08-08 12:46:34 +08:00
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 " ) )
2017-04-12 22:52:52 +08:00
end
2018-08-24 09:30:00 +08:00
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
2017-04-12 22:52:52 +08:00
it 'should correct bad html in body_tag_baked and head_tag_baked' do
2017-05-03 04:01:01 +08:00
theme . set_field ( target : :common , name : " head_tag " , value : " <b>I am bold " )
2017-04-12 22:52:52 +08:00
theme . save!
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( theme . id , :desktop , " head_tag " ) ) . to eq ( " <b>I am bold</b> " )
2017-04-12 22:52:52 +08:00
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
2017-05-03 04:01:01 +08:00
theme . set_field ( target : :common , name : " header " , value : with_template )
2017-04-12 22:52:52 +08:00
theme . save!
2018-10-15 12:55:23 +08:00
field = theme . theme_fields . find_by ( target_id : Theme . targets [ :common ] , name : 'header' )
2018-07-12 12:18:21 +08:00
baked = Theme . lookup_field ( theme . id , :mobile , " header " )
2017-04-12 22:52:52 +08:00
2018-10-15 12:55:23 +08:00
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' )
2017-04-12 22:52:52 +08:00
end
2019-02-13 00:14:43 +08:00
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
2017-04-12 22:52:52 +08:00
it 'should create body_tag_baked on demand if needed' do
2017-05-03 04:01:01 +08:00
theme . set_field ( target : :common , name : :body_tag , value : " <b>test " )
2017-04-12 22:52:52 +08:00
theme . save
ThemeField . update_all ( value_baked : nil )
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( theme . id , :desktop , :body_tag ) ) . to match ( / <b>test< \/ b> / )
2017-04-12 22:52:52 +08:00
end
2018-08-24 09:30:00 +08:00
describe " # switch_to_component! " do
it " correctly converts a theme to component " do
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
2018-08-24 09:30:00 +08:00
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
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
2018-08-24 09:30:00 +08:00
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
2018-08-08 12:46:34 +08:00
describe " .transform_ids " do
2019-01-25 22:19:01 +08:00
let! ( :orphan1 ) { Fabricate ( :theme , component : true ) }
2018-08-24 09:30:00 +08:00
let! ( :child ) { Fabricate ( :theme , component : true ) }
let! ( :child2 ) { Fabricate ( :theme , component : true ) }
2019-01-25 22:19:01 +08:00
let! ( :orphan2 ) { Fabricate ( :theme , component : true ) }
let! ( :orphan3 ) { Fabricate ( :theme , component : true ) }
let! ( :orphan4 ) { Fabricate ( :theme , component : true ) }
2018-08-08 12:46:34 +08:00
2018-08-24 09:30:00 +08:00
before do
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
theme . add_relative_theme! ( :child , child2 )
2018-08-24 09:30:00 +08:00
end
2019-01-25 22:19:01 +08:00
it " returns an empty array if no ids are passed " do
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( nil ) ) . to eq ( [ ] )
2019-01-25 22:19:01 +08:00
end
2018-08-24 09:30:00 +08:00
it " adds the child themes of the parent " do
sorted = [ child . id , child2 . id ] . sort
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ theme . id , * sorted ] )
2018-08-08 12:46:34 +08:00
end
end
2017-04-12 22:52:52 +08:00
context " plugin api " do
def transpile ( html )
2018-03-05 08:04:23 +08:00
f = ThemeField . create! ( target_id : Theme . targets [ :mobile ] , theme_id : 1 , name : " after_header " , value : html )
2019-04-12 18:36:08 +08:00
f . ensure_baked!
2021-04-12 20:02:58 +08:00
[ f . value_baked , f . javascript_cache , f ]
2017-04-12 22:52:52 +08:00
end
it " transpiles ES6 code " do
html = <<HTML
< script type = 'text/discourse-plugin' version = '0.1' >
const x = 1 ;
< / script>
HTML
2021-04-12 20:02:58 +08:00
baked , javascript_cache , field = transpile ( html )
2018-10-15 12:55:23 +08:00
expect ( baked ) . to include ( javascript_cache . url )
2021-04-12 20:02:58 +08:00
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; " )
2017-04-12 22:52:52 +08:00
end
DEV: updates js transpiler to use babel 7 (#10627)
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)
2020-09-15 15:26:33 +08:00
it " wraps constants calls in a readOnlyError function " do
2017-04-12 22:52:52 +08:00
html = <<HTML
< script type = 'text/discourse-plugin' version = '0.1' >
const x = 1 ;
x = 2 ;
< / script>
HTML
2018-10-15 12:55:23 +08:00
baked , javascript_cache = transpile ( html )
expect ( baked ) . to include ( javascript_cache . url )
DEV: updates js transpiler to use babel 7 (#10627)
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)
2020-09-15 15:26:33 +08:00
expect ( javascript_cache . content ) . to include ( 'var x = 1;' )
expect ( javascript_cache . content ) . to include ( 'x = (_readOnlyError("x"), 2);' )
2017-04-12 22:52:52 +08:00
end
end
2019-01-18 20:03:55 +08:00
context 'theme upload vars' do
2017-05-08 23:38:48 +08:00
let :image do
file_from_fixtures ( " logo.png " )
end
it 'can handle uploads based of ThemeField' do
2017-05-11 06:16:57 +08:00
upload = UploadCreator . new ( image , " logo.png " ) . create_for ( - 1 )
2017-05-08 23:38:48 +08:00
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
2017-05-10 05:20:28 +08:00
# no error for theme field
theme . reload
expect ( theme . theme_fields . find_by ( name : :scss ) . error ) . to eq ( nil )
2017-05-08 23:38:48 +08:00
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : theme . id )
scss , _map = Stylesheet :: Manager :: Builder . new (
target : :desktop_theme , theme : theme , manager : manager
) . compile ( force : true )
2017-05-08 23:38:48 +08:00
expect ( scss ) . to include ( upload . url )
end
2017-05-03 04:01:01 +08:00
end
2018-03-05 08:04:23 +08:00
context " theme settings " do
2018-03-05 12:35:41 +08:00
it " allows values to be used in scss " do
2018-03-05 08:04:23 +08:00
theme . set_field ( target : :settings , name : :yaml , value : " background_color: red \n font_size: 25px " )
theme . set_field ( target : :common , name : :scss , value : 'body {background-color: $background_color; font-size: $font-size}' )
theme . save!
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : theme . id )
scss , _map = Stylesheet :: Manager :: Builder . new (
target : :desktop_theme , theme : theme , manager : manager
) . compile ( force : true )
2018-03-05 08:04:23 +08:00
expect ( scss ) . to include ( " background-color:red " )
expect ( scss ) . to include ( " font-size:25px " )
2018-03-05 12:35:41 +08:00
setting = theme . settings . find { | s | s . name == :font_size }
setting . value = '30px'
2019-04-12 18:36:08 +08:00
theme . save!
2018-03-05 12:35:41 +08:00
2021-06-15 14:57:17 +08:00
scss , _map = Stylesheet :: Manager :: Builder . new (
target : :desktop_theme , theme : theme , manager : manager
) . compile ( force : true )
2018-03-05 12:35:41 +08:00
expect ( scss ) . to include ( " font-size:30px " )
2019-03-08 16:58:06 +08:00
# 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!
2021-06-15 14:57:17 +08:00
scss , _map = Stylesheet :: Manager :: Builder . new (
target : :desktop_theme , theme : theme , manager : manager
) . compile ( force : true )
2019-03-08 16:58:06 +08:00
expect ( scss ) . to include ( 'font-size:"#{$fakeinterpolatedvariable}\a andanothervalue \'withquotes\'; margin: 0;\a"' )
2018-03-05 08:04:23 +08:00
end
2018-03-05 12:35:41 +08:00
it " allows values to be used in JS " do
2019-05-24 22:25:55 +08:00
theme . name = 'awesome theme"'
2018-03-05 12:35:41 +08:00
theme . set_field ( target : :settings , name : :yaml , value : " name: bob " )
2018-10-15 12:55:23 +08:00
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>' )
2018-03-05 12:35:41 +08:00
theme . save!
2018-10-15 12:55:23 +08:00
theme_field . reload
expect ( Theme . lookup_field ( theme . id , :desktop , :after_header ) ) . to include ( theme_field . javascript_cache . url )
2021-04-12 20:02:58 +08:00
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() {} " )
2018-03-05 12:35:41 +08:00
setting = theme . settings . find { | s | s . name == :name }
setting . value = 'bill'
2019-04-12 18:36:08 +08:00
theme . save!
2018-03-05 12:35:41 +08:00
2018-10-15 12:55:23 +08:00
theme_field . reload
2021-04-12 20:02:58 +08:00
expect ( theme_field . javascript_cache . content ) . to include (
" require( \" discourse/lib/theme-settings-store \" ).registerSettings( #{ theme_field . theme . id } , { \" name \" : \" bill \" }); "
)
2018-10-15 12:55:23 +08:00
expect ( Theme . lookup_field ( theme . id , :desktop , :after_header ) ) . to include ( theme_field . javascript_cache . url )
2018-03-05 12:35:41 +08:00
end
2018-12-19 23:36:31 +08:00
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
2018-03-05 08:04:23 +08:00
end
2018-07-12 12:18:21 +08:00
it 'correctly caches theme ids' do
2017-05-03 23:31:16 +08:00
Theme . destroy_all
2018-08-08 12:46:34 +08:00
theme
theme2 = Fabricate ( :theme )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
expect ( Theme . theme_ids ) . to contain_exactly ( theme . id , theme2 . id )
expect ( Theme . user_theme_ids ) . to eq ( [ ] )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
theme . update! ( user_selectable : true )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
expect ( Theme . user_theme_ids ) . to contain_exactly ( theme . id )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
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 )
2017-04-15 01:35:12 +08:00
theme . set_default!
2018-08-08 12:46:34 +08:00
expect ( Theme . user_theme_ids ) . to contain_exactly ( theme . id )
2017-04-15 01:35:12 +08:00
theme . destroy
2018-08-08 12:46:34 +08:00
theme2 . destroy
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
expect ( Theme . theme_ids ) . to eq ( [ ] )
expect ( Theme . user_theme_ids ) . to eq ( [ ] )
2017-04-15 01:35:12 +08:00
end
2017-09-04 19:27:58 +08:00
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 ( [ ] )
2018-08-08 12:46:34 +08:00
theme = Fabricate ( :theme , name : " bob " , user_selectable : true )
2019-04-12 18:36:08 +08:00
theme . save!
2017-09-04 19:27:58 +08:00
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
2018-07-12 12:18:21 +08:00
def cached_settings ( id )
Theme . find_by ( id : id ) . included_settings . to_json
2018-03-05 08:04:23 +08:00
end
2018-10-16 09:00:33 +08:00
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
2019-05-08 23:02:55 +08:00
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
2019-08-21 14:50:47 +08:00
it 'includes theme_uploads in settings' do
Theme . destroy_all
2021-02-03 02:09:41 +08:00
upload = UploadCreator . new ( file_from_fixtures ( " logo.png " ) , " logo.png " ) . create_for ( - 1 )
2019-08-21 14:50:47 +08:00
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
2021-06-23 10:34:22 +08:00
it 'does not break on missing 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!
Upload . find ( upload . id ) . destroy
theme . clear_cached_settings!
json = JSON . parse ( cached_settings ( theme . id ) )
expect ( json ) . to be_empty
end
2021-04-21 01:25:35 +08:00
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
2021-04-21 06:42:02 +08:00
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
2018-03-05 08:04:23 +08:00
it 'handles settings cache correctly' do
Theme . destroy_all
2018-07-12 12:18:21 +08:00
expect ( cached_settings ( theme . id ) ) . to eq ( " {} " )
2018-03-05 08:04:23 +08:00
theme . set_field ( target : :settings , name : " yaml " , value : " boolean_setting: true " )
theme . save!
2018-07-12 12:18:21 +08:00
expect ( cached_settings ( theme . id ) ) . to match ( / \ "boolean_setting \ ":true / )
2018-03-05 08:04:23 +08:00
theme . settings . first . value = " false "
2019-04-12 18:36:08 +08:00
theme . save!
2018-07-12 12:18:21 +08:00
expect ( cached_settings ( theme . id ) ) . to match ( / \ "boolean_setting \ ":false / )
2018-03-05 08:04:23 +08:00
child . set_field ( target : :settings , name : " yaml " , value : " integer_setting: 54 " )
child . save!
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
2018-03-05 08:04:23 +08:00
2018-07-12 12:18:21 +08:00
json = cached_settings ( theme . id )
2018-03-05 08:04:23 +08:00
expect ( json ) . to match ( / \ "boolean_setting \ ":false / )
expect ( json ) . to match ( / \ "integer_setting \ ":54 / )
2018-07-12 12:18:21 +08:00
expect ( cached_settings ( child . id ) ) . to eq ( " { \" integer_setting \" :54} " )
2018-03-05 08:04:23 +08:00
child . destroy!
2018-07-12 12:18:21 +08:00
json = cached_settings ( theme . id )
2018-03-05 08:04:23 +08:00
expect ( json ) . not_to match ( / \ "integer_setting \ ":54 / )
expect ( json ) . to match ( / \ "boolean_setting \ ":false / )
end
2019-01-17 19:46:11 +08:00
2021-03-11 09:15:04 +08:00
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
2021-03-15 12:36:10 +08:00
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
2021-03-11 09:15:04 +08:00
end
end
2019-01-17 19:46:11 +08:00
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 :
2019-01-25 22:19:01 +08:00
theme_metadata :
description : " Description of my theme "
2019-01-17 19:46:11 +08:00
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
2019-01-25 22:19:01 +08:00
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
2019-01-17 19:46:11 +08:00
it " can create a hash of overridden values " do
2020-05-07 04:57:14 +08:00
en_translation = ThemeField . create! ( theme_id : theme . id , name : " en " , type_id : ThemeField . types [ :yaml ] , target_id : Theme . targets [ :translations ] , value : << ~ YAML )
en :
2019-01-17 19:46:11 +08:00
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 (
2020-05-07 04:57:14 +08:00
" en " = > {
2019-01-17 19:46:11 +08:00
" 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
2019-07-16 22:34:33 +08:00
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 )
2020-05-29 20:04:51 +08:00
Theme . stubs ( :compiler_version ) . returns ( " SOME_NEW_HASH " ) do
2019-07-16 22:34:33 +08:00
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
2020-05-29 20:04:51 +08:00
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
2019-07-16 22:34:33 +08:00
end
2021-02-03 02:09:41 +08:00
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 ) {
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : theme . id )
builder = Stylesheet :: Manager :: Builder . new (
target : :desktop_theme , theme : theme , manager : manager
)
builder . compile ( force : true )
2021-02-03 02:09:41 +08:00
}
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
2021-02-27 01:30:23 +08:00
it " works for child themes " do
2021-02-03 02:09:41 +08:00
child_theme . set_field ( target : :common , name : :scss , value : '@import "my_files/moremagic"' )
child_theme . save!
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : child_theme . id )
builder = Stylesheet :: Manager :: Builder . new (
target : :desktop_theme , theme : child_theme , manager : manager
)
css , _map = builder . compile ( force : true )
2021-02-03 02:09:41 +08:00
expect ( css ) . to include ( " body{background:green} " )
2021-02-04 21:51:18 +08:00
end
2021-02-03 02:09:41 +08:00
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 \n font_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
2021-04-29 04:12:08 +08:00
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
2017-04-12 22:52:52 +08:00
end