# frozen_string_literal: true RSpec.describe ThemeJavascriptCompiler do let(:compiler) { ThemeJavascriptCompiler.new(1, 'marks') } describe "#append_raw_template" do it 'uses the correct template paths' do template = "

hello

" name = "/path/to/templates1" compiler.append_raw_template("#{name}.raw", template) expect(compiler.raw_content.to_s).to include("addRawTemplate(\"#{name}\"") name = "/path/to/templates2" compiler.append_raw_template("#{name}.hbr", template) expect(compiler.raw_content.to_s).to include("addRawTemplate(\"#{name}\"") name = "/path/to/templates3" compiler.append_raw_template("#{name}.hbs", template) expect(compiler.raw_content.to_s).to include("addRawTemplate(\"#{name}.hbs\"") end end describe "#append_ember_template" do it 'maintains module names so that discourse-boot.js can correct them' do compiler.append_ember_template("/connectors/blah-1", "{{var}}") expect(compiler.raw_content.to_s).to include("define(\"discourse/theme-1/connectors/blah-1\", [\"exports\", \"@ember/template-factory\"]") compiler.append_ember_template("connectors/blah-2", "{{var}}") expect(compiler.raw_content.to_s).to include("define(\"discourse/theme-1/connectors/blah-2\", [\"exports\", \"@ember/template-factory\"]") compiler.append_ember_template("javascripts/connectors/blah-3", "{{var}}") expect(compiler.raw_content.to_s).to include("define(\"discourse/theme-1/javascripts/connectors/blah-3\", [\"exports\", \"@ember/template-factory\"]") end end describe "connector module name handling" do it 'separates colocated connectors to avoid module name clash' do # Colocated under `/connectors` compiler = ThemeJavascriptCompiler.new(1, 'marks') compiler.append_tree({ "connectors/outlet/blah-1.hbs" => "{{var}}", "connectors/outlet/blah-1.js" => "console.log('test')" }) expect(compiler.raw_content.to_s).to include("discourse/theme-1/connectors/outlet/blah-1") expect(compiler.raw_content.to_s).to include("discourse/theme-1/templates/connectors/outlet/blah-1") expect(JSON.parse(compiler.source_map)["sources"]).to contain_exactly("connectors/outlet/blah-1.js", "templates/connectors/outlet/blah-1.js") # Colocated under `/templates/connectors` compiler = ThemeJavascriptCompiler.new(1, 'marks') compiler.append_tree({ "templates/connectors/outlet/blah-1.hbs" => "{{var}}", "templates/connectors/outlet/blah-1.js" => "console.log('test')" }) expect(compiler.raw_content.to_s).to include("discourse/theme-1/connectors/outlet/blah-1") expect(compiler.raw_content.to_s).to include("discourse/theme-1/templates/connectors/outlet/blah-1") expect(JSON.parse(compiler.source_map)["sources"]).to contain_exactly("connectors/outlet/blah-1.js", "templates/connectors/outlet/blah-1.js") # Not colocated compiler = ThemeJavascriptCompiler.new(1, 'marks') compiler.append_tree({ "templates/connectors/outlet/blah-1.hbs" => "{{var}}", "connectors/outlet/blah-1.js" => "console.log('test')" }) expect(compiler.raw_content.to_s).to include("discourse/theme-1/connectors/outlet/blah-1") expect(compiler.raw_content.to_s).to include("discourse/theme-1/templates/connectors/outlet/blah-1") expect(JSON.parse(compiler.source_map)["sources"]).to contain_exactly("connectors/outlet/blah-1.js", "templates/connectors/outlet/blah-1.js") end end describe "error handling" do it "handles syntax errors in raw templates" do expect do compiler.append_raw_template("sometemplate.hbr", "{{invalidtemplate") end.to raise_error(ThemeJavascriptCompiler::CompileError, /Parse error on line 1/) end it "handles syntax errors in ember templates" do expect do compiler.append_ember_template("sometemplate", "{{invalidtemplate") end.to raise_error(ThemeJavascriptCompiler::CompileError, /Parse error on line 1/) end end describe "#append_tree" do it "can handle multiple modules" do compiler.append_tree( { "discourse/components/mycomponent.js" => <<~JS, import Component from "@glimmer/component"; export default class MyComponent extends Component {} JS "discourse/templates/components/mycomponent.hbs" => "{{my-component-template}}" } ) expect(compiler.raw_content).to include('define("discourse/theme-1/components/mycomponent"') expect(compiler.raw_content).to include('define("discourse/theme-1/discourse/templates/components/mycomponent"') end it "handles colocated components" do compiler.append_tree( { "discourse/components/mycomponent.js" => <<~JS, import Component from "@glimmer/component"; export default class MyComponent extends Component {} JS "discourse/components/mycomponent.hbs" => "{{my-component-template}}" } ) expect(compiler.raw_content).to include("__COLOCATED_TEMPLATE__ =") expect(compiler.raw_content).to include("setComponentTemplate") end it "handles colocated admin components" do compiler.append_tree( { "admin/components/mycomponent.js" => <<~JS, import Component from "@glimmer/component"; export default class MyComponent extends Component {} JS "admin/components/mycomponent.hbs" => "{{my-component-template}}" } ) expect(compiler.raw_content).to include("__COLOCATED_TEMPLATE__ =") expect(compiler.raw_content).to include("setComponentTemplate") end it "applies theme AST transforms to colocated components" do compiler = ThemeJavascriptCompiler.new(12345678910, 'my theme name') compiler.append_tree( { "discourse/components/mycomponent.hbs" => '{{theme-i18n "my_translation_key"}}' } ) template_compiled_line = compiler.raw_content.lines.find { |l| l.include?('"block":') } expect(template_compiled_line).to include("12345678910") end it "prints error when default export missing" do compiler.append_tree( { "discourse/components/mycomponent.js" => <<~JS, import Component from "@glimmer/component"; class MyComponent extends Component {} JS "discourse/components/mycomponent.hbs" => "{{my-component-template}}" } ) expect(compiler.raw_content).to include("__COLOCATED_TEMPLATE__ =") expect(compiler.raw_content).to include("throw new Error") end it "handles template-only components" do compiler.append_tree( { "discourse/components/mycomponent.hbs" => "{{my-component-template}}" } ) expect(compiler.raw_content).to include("__COLOCATED_TEMPLATE__ =") expect(compiler.raw_content).to include("setComponentTemplate") expect(compiler.raw_content).to include("@ember/component/template-only") end end describe "terser compilation" do it "applies terser and provides sourcemaps" do sources = { "multiply.js" => "let multiply = (firstValue, secondValue) => firstValue * secondValue;", "add.js" => "let add = (firstValue, secondValue) => firstValue + secondValue;" } compiler.append_tree(sources) expect(compiler.content).to include("multiply") expect(compiler.content).to include("add") map = JSON.parse(compiler.source_map) expect(map["sources"]).to contain_exactly(*sources.keys) expect(map["sourcesContent"].to_s).to include("let multiply") expect(map["sourcesContent"].to_s).to include("let add") expect(map["sourceRoot"]).to eq("theme-1/") end it "handles invalid JS" do compiler.append_raw_script("filename.js", "if(someCondition") expect(compiler.content).to include('console.error("[THEME 1') expect(compiler.content).to include('Unexpected token') end end end