# frozen_string_literal: true

require 'stylesheet/compiler'

describe Stylesheet::Manager do
  def manager(theme_id = nil)
    Stylesheet::Manager.new(theme_id: theme_id)
  end

  it 'does not crash for missing theme' do
    Theme.clear_default!
    link = manager.stylesheet_link_tag(:embedded_theme)
    expect(link).to eq("")
  end

  it "still returns something for no themes" do
    link = manager.stylesheet_link_tag(:desktop, 'all')
    expect(link).not_to eq("")
  end

  context "themes with components" do
    let(:child_theme) { Fabricate(:theme, component: true, name: "a component").tap { |c|
      c.set_field(target: :common, name: "scss", value: ".child_common{.scss{color: red;}}")
      c.set_field(target: :desktop, name: "scss", value: ".child_desktop{.scss{color: red;}}")
      c.set_field(target: :mobile, name: "scss", value: ".child_mobile{.scss{color: red;}}")
      c.set_field(target: :common, name: "embedded_scss", value: ".child_embedded{.scss{color: red;}}")
      c.save!
    }}

    let(:theme) { Fabricate(:theme).tap { |t|
      t.set_field(target: :common, name: "scss", value: ".common{.scss{color: red;}}")
      t.set_field(target: :desktop, name: "scss", value: ".desktop{.scss{color: red;}}")
      t.set_field(target: :mobile, name: "scss", value: ".mobile{.scss{color: red;}}")
      t.set_field(target: :common, name: "embedded_scss", value: ".embedded{.scss{color: red;}}")
      t.save!

      t.add_relative_theme!(:child, child_theme)
    }}

    it "generates the right links for non-theme targets" do
      manager = manager(nil)

      hrefs = manager.stylesheet_details(:desktop, 'all')

      expect(hrefs.length).to eq(1)
    end

    it 'can correctly compile theme css' do
      manager = manager(theme.id)
      old_links = manager.stylesheet_link_tag(:desktop_theme, 'all')

      builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: theme, manager: manager
      )

      builder.compile(force: true)

      css = File.read(builder.stylesheet_fullpath)
      _source_map = File.read(builder.source_map_fullpath)

      expect(css).to match(/\.common/)
      expect(css).to match(/\.desktop/)

      # child theme CSS is no longer bundled with main theme
      expect(css).not_to match(/child_common/)
      expect(css).not_to match(/child_desktop/)

      child_theme_builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: child_theme, manager: manager
      )

      child_theme_builder.compile(force: true)

      child_css = File.read(child_theme_builder.stylesheet_fullpath)
      _child_source_map = File.read(child_theme_builder.source_map_fullpath)

      expect(child_css).to match(/child_common/)
      expect(child_css).to match(/child_desktop/)

      child_theme.set_field(target: :desktop, name: :scss, value: ".nothing{color: green;}")
      child_theme.save!

      new_links = manager(theme.id).stylesheet_link_tag(:desktop_theme, 'all')

      expect(new_links).not_to eq(old_links)

      # our theme better have a name with the theme_id as part of it
      expect(new_links).to include("/stylesheets/desktop_theme_#{theme.id}_")
      expect(new_links).to include("/stylesheets/desktop_theme_#{child_theme.id}_")
    end

    it 'can correctly compile embedded theme css' do
      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :embedded_theme, theme: theme, manager: manager
      )

      builder.compile(force: true)

      css = File.read(builder.stylesheet_fullpath)
      expect(css).to match(/\.embedded/)
      expect(css).not_to match(/\.child_embedded/)

      child_theme_builder = Stylesheet::Manager::Builder.new(
        target: :embedded_theme,
        theme: child_theme,
        manager: manager
      )

      child_theme_builder.compile(force: true)

      css = File.read(child_theme_builder.stylesheet_fullpath)
      expect(css).to match(/\.child_embedded/)
    end

    it 'includes both parent and child theme assets' do
      manager = manager(theme.id)

      hrefs = manager.stylesheet_details(:desktop_theme, 'all')

      expect(hrefs.count).to eq(2)

      expect(hrefs.map { |href| href[:theme_id] }).to contain_exactly(
        theme.id, child_theme.id
      )

      hrefs = manager.stylesheet_details(:embedded_theme, 'all')

      expect(hrefs.count).to eq(2)

      expect(hrefs.map { |href| href[:theme_id] }).to contain_exactly(
        theme.id, child_theme.id
      )
    end

    it "includes the escaped theme name" do
      manager = manager(theme.id)

      theme.update(name: "a strange name\"with a quote in it")

      tag = manager.stylesheet_link_tag(:desktop_theme)
      expect(tag).to have_tag("link", with: {
        "data-theme-name" => theme.name.downcase
      })
      expect(tag).to have_tag("link", with: {
        "data-theme-name" => child_theme.name.downcase
      })
    end

    context "stylesheet order" do
      let(:z_child_theme) do
        Fabricate(:theme, component: true, name: "ze component").tap do |z|
          z.set_field(target: :desktop, name: "scss", value: ".child_desktop{.scss{color: red;}}")
          z.save!
        end
      end

      let(:remote) { RemoteTheme.create!(remote_url: "https://github.com/org/remote-theme1") }

      let(:child_remote) do
        Fabricate(:theme, remote_theme: remote, component: true).tap do |t|
          t.set_field(target: :desktop, name: "scss", value: ".child_desktop{.scss{color: red;}}")
          t.save!
        end
      end

      it 'output remote child, then sort children alphabetically, then local parent' do
        theme.add_relative_theme!(:child, z_child_theme)
        theme.add_relative_theme!(:child, child_remote)

        manager = manager(theme.id)
        hrefs = manager.stylesheet_details(:desktop_theme, 'all')

        parent = hrefs.select { |href| href[:theme_id] == theme.id }.first
        child_a = hrefs.select { |href| href[:theme_id] == child_theme.id }.first
        child_z = hrefs.select { |href| href[:theme_id] == z_child_theme.id }.first
        child_r = hrefs.select { |href| href[:theme_id] == child_remote.id }.first

        child_local_A = "<link href=\"#{child_a[:new_href]}\" data-theme-id=\"#{child_a[:theme_id]}\" data-theme-name=\"#{child_a[:theme_name]}\"/>"
        child_local_Z = "<link href=\"#{child_z[:new_href]}\" data-theme-id=\"#{child_z[:theme_id]}\" data-theme-name=\"#{child_z[:theme_name]}\"/>"
        child_remote_R = "<link href=\"#{child_r[:new_href]}\" data-theme-id=\"#{child_r[:theme_id]}\" data-theme-name=\"#{child_r[:theme_name]}\"/>"
        parent_local = "<link href=\"#{parent[:new_href]}\" data-theme-id=\"#{parent[:theme_id]}\" data-theme-name=\"#{parent[:theme_name]}\"/>"

        link_hrefs = manager.stylesheet_link_tag(:desktop_theme).gsub('media="all" rel="stylesheet" data-target="desktop_theme" ', '')

        expect(link_hrefs).to eq([child_remote_R, child_local_A, child_local_Z, parent_local].join("\n").html_safe)
      end

      it "output remote child, remote parent, local child" do
        remote2 = RemoteTheme.create!(remote_url: "https://github.com/org/remote-theme2")
        remote_main_theme = Fabricate(:theme, remote_theme: remote2, name: "remote main").tap do |t|
          t.set_field(target: :desktop, name: "scss", value: ".el{color: red;}")
          t.save!
        end

        remote_main_theme.add_relative_theme!(:child, z_child_theme)
        remote_main_theme.add_relative_theme!(:child, child_remote)

        manager = manager(remote_main_theme.id)
        hrefs = manager.stylesheet_details(:desktop_theme, 'all')

        parent_r = hrefs.select { |href| href[:theme_id] == remote_main_theme.id }.first
        child_z = hrefs.select { |href| href[:theme_id] == z_child_theme.id }.first
        child_r = hrefs.select { |href| href[:theme_id] == child_remote.id }.first

        parent_remote = "<link href=\"#{parent_r[:new_href]}\" data-theme-id=\"#{parent_r[:theme_id]}\" data-theme-name=\"#{parent_r[:theme_name]}\"/>"
        child_local = "<link href=\"#{child_z[:new_href]}\" data-theme-id=\"#{child_z[:theme_id]}\" data-theme-name=\"#{child_z[:theme_name]}\"/>"
        child_remote = "<link href=\"#{child_r[:new_href]}\" data-theme-id=\"#{child_r[:theme_id]}\" data-theme-name=\"#{child_r[:theme_name]}\"/>"

        link_hrefs = manager.stylesheet_link_tag(:desktop_theme).gsub('media="all" rel="stylesheet" data-target="desktop_theme" ', '')
        expect(link_hrefs).to eq([child_remote, parent_remote, child_local].join("\n").html_safe)
      end
    end

    it 'outputs tags for non-theme targets for theme component' do
      child_theme = Fabricate(:theme, component: true)

      hrefs = manager(child_theme.id).stylesheet_details(:desktop, 'all')

      expect(hrefs.count).to eq(1) # desktop
    end

    it 'does not output tags for component targets with no styles' do
      embedded_scss_child = Fabricate(:theme, component: true)
      embedded_scss_child.set_field(target: :common, name: "embedded_scss", value: ".scss{color: red;}")
      embedded_scss_child.save!

      theme.add_relative_theme!(:child, embedded_scss_child)

      manager = manager(theme.id)

      hrefs = manager.stylesheet_details(:desktop_theme, 'all')
      expect(hrefs.count).to eq(2) # theme + child_theme

      hrefs = manager.stylesheet_details(:embedded_theme, 'all')
      expect(hrefs.count).to eq(3) # theme + child_theme + embedded_scss_child
    end

    it '.stylesheet_details can find components mobile SCSS when target is `:mobile_theme`' do
      child_with_mobile_scss = Fabricate(:theme, component: true)
      child_with_mobile_scss.set_field(target: :mobile, name: :scss, value: "body { color: red; }")
      child_with_mobile_scss.save!
      theme.add_relative_theme!(:child, child_with_mobile_scss)

      manager = manager(theme.id)
      hrefs = manager.stylesheet_details(:mobile_theme, 'all')

      expect(hrefs.count).to eq(3)
      expect(hrefs.find { |h| h[:theme_id] == child_with_mobile_scss.id }).to be_present
    end

    it 'does not output multiple assets for non-theme targets' do
      manager = manager()

      hrefs = manager.stylesheet_details(:admin, 'all')
      expect(hrefs.count).to eq(1)

      hrefs = manager.stylesheet_details(:mobile, 'all')
      expect(hrefs.count).to eq(1)
    end
  end

  describe 'digest' do
    after do
      DiscoursePluginRegistry.reset!
    end

    it 'can correctly account for plugins in default digest' do
      builder = Stylesheet::Manager::Builder.new(target: :desktop, manager: manager)
      digest1 = builder.digest

      DiscoursePluginRegistry.stylesheets["fake"] = Set.new(["fake_file"])
      builder = Stylesheet::Manager::Builder.new(target: :desktop, manager: manager)
      digest2 = builder.digest

      expect(digest1).not_to eq(digest2)
    end

    it "can correctly account for settings in theme's components" do
      theme = Fabricate(:theme)
      child = Fabricate(:theme, component: true)
      theme.add_relative_theme!(:child, child)

      child.set_field(target: :settings, name: :yaml, value: "childcolor: red")
      child.set_field(target: :common, name: :scss, value: "body {background-color: $childcolor}")
      child.save!

      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: theme, manager: manager
      )

      digest1 = builder.digest

      child.update_setting(:childcolor, "green")

      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: theme, manager: manager
      )

      digest2 = builder.digest

      expect(digest1).not_to eq(digest2)
    end

    let(:image) { file_from_fixtures("logo.png") }
    let(:image2) { file_from_fixtures("logo-dev.png") }

    it 'can correctly account for theme uploads in digest' do
      theme = Fabricate(:theme)

      upload = UploadCreator.new(image, "logo.png").create_for(-1)
      field = ThemeField.create!(
        theme_id: theme.id,
        target_id: Theme.targets[:common],
        name: "logo",
        value: "",
        upload_id: upload.id,
        type_id: ThemeField.types[:theme_upload_var]
      )

      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: theme, manager: manager
      )

      digest1 = builder.digest
      field.destroy!

      upload = UploadCreator.new(image2, "logo.png").create_for(-1)
      field = ThemeField.create!(
        theme_id: theme.id,
        target_id: Theme.targets[:common],
        name: "logo",
        value: "",
        upload_id: upload.id,
        type_id: ThemeField.types[:theme_upload_var]
      )

      builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: theme.reload, manager: manager
      )

      digest2 = builder.digest

      expect(digest1).not_to eq(digest2)
    end

    it 'returns different digest based on target' do
      theme = Fabricate(:theme)
      builder = Stylesheet::Manager::Builder.new(target: :desktop_theme, theme: theme, manager: manager)
      expect(builder.digest).to eq(builder.theme_digest)

      builder = Stylesheet::Manager::Builder.new(target: :color_definitions, manager: manager)
      expect(builder.digest).to eq(builder.color_scheme_digest)

      builder = Stylesheet::Manager::Builder.new(target: :admin, manager: manager)
      expect(builder.digest).to eq(builder.default_digest)

      builder = Stylesheet::Manager::Builder.new(target: :desktop, manager: manager)
      expect(builder.digest).to eq(builder.default_digest)
    end

    it 'returns different digest based on hostname' do
      theme = Fabricate(:theme)

      SiteSetting.force_hostname = "host1.example.com"
      initial_theme_digest = Stylesheet::Manager::Builder.new(target: :desktop_theme, theme: theme, manager: manager).digest
      initial_color_scheme_digest = Stylesheet::Manager::Builder.new(target: :color_definitions, manager: manager).digest
      initial_default_digest = Stylesheet::Manager::Builder.new(target: :desktop, manager: manager).digest

      SiteSetting.force_hostname = "host2.example.com"
      new_theme_digest = Stylesheet::Manager::Builder.new(target: :desktop_theme, theme: theme, manager: manager).digest
      new_color_scheme_digest = Stylesheet::Manager::Builder.new(target: :color_definitions, manager: manager).digest
      new_default_digest = Stylesheet::Manager::Builder.new(target: :desktop, manager: manager).digest

      expect(initial_theme_digest).not_to eq(new_theme_digest)
      expect(initial_color_scheme_digest).not_to eq(new_color_scheme_digest)
      expect(initial_default_digest).not_to eq(new_default_digest)
    end
  end

  describe 'color_scheme_digest' do
    fab!(:theme) { Fabricate(:theme) }

    it "changes with category background image" do
      category1 = Fabricate(:category, uploaded_background_id: 123, updated_at: 1.week.ago)
      category2 = Fabricate(:category, uploaded_background_id: 456, updated_at: 2.days.ago)

      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: theme, manager: manager
      )

      digest1 = builder.color_scheme_digest

      category2.update!(uploaded_background_id: 789, updated_at: 1.day.ago)

      digest2 = builder.color_scheme_digest
      expect(digest2).to_not eq(digest1)

      category1.update!(uploaded_background_id: nil, updated_at: 5.minutes.ago)

      digest3 = builder.color_scheme_digest
      expect(digest3).to_not eq(digest2)
      expect(digest3).to_not eq(digest1)
    end

    it "updates digest when updating a color scheme" do
      scheme = ColorScheme.create_from_base(name: "Neutral", base_scheme_id: "Neutral")
      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )

      digest1 = builder.color_scheme_digest

      ColorSchemeRevisor.revise(scheme, colors: [{ name: "primary", hex: "CC0000" }])

      digest2 = builder.color_scheme_digest

      expect(digest1).to_not eq(digest2)
    end

    it "updates digest when updating a theme's color definitions" do
      scheme = ColorScheme.base
      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )

      digest1 = builder.color_scheme_digest

      theme.set_field(target: :common, name: :color_definitions, value: 'body {color: brown}')
      theme.save!

      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )

      digest2 = builder.color_scheme_digest

      expect(digest1).to_not eq(digest2)
    end

    it "updates digest when updating a theme component's color definitions" do
      scheme = ColorScheme.base
      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )

      digest1 = builder.color_scheme_digest

      child_theme = Fabricate(:theme, component: true)
      child_theme.set_field(target: :common, name: "color_definitions", value: 'body {color: fuchsia}')
      child_theme.save!
      theme.add_relative_theme!(:child, child_theme)
      theme.save!

      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )

      digest2 = builder.color_scheme_digest
      expect(digest1).to_not eq(digest2)

      child_theme.set_field(target: :common, name: "color_definitions", value: 'body {color: blue}')
      child_theme.save!

      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )

      digest3 = builder.color_scheme_digest
      expect(digest2).to_not eq(digest3)
    end

    it "updates digest when setting fonts" do
      manager = manager(theme.id)
      builder = Stylesheet::Manager::Builder.new(
        target: :desktop_theme, theme: theme, manager: manager
      )
      digest1 = builder.color_scheme_digest
      SiteSetting.base_font = DiscourseFonts.fonts[2][:key]
      digest2 = builder.color_scheme_digest

      expect(digest1).to_not eq(digest2)

      SiteSetting.heading_font = DiscourseFonts.fonts[4][:key]
      digest3 = builder.color_scheme_digest

      expect(digest3).to_not eq(digest2)
    end

  end

  describe 'color_scheme_stylesheets' do
    it "returns something by default" do
      link = manager.color_scheme_stylesheet_link_tag
      expect(link).to include("color_definitions_base")
    end

    it "does not crash when no default theme is set" do
      SiteSetting.default_theme_id = -1
      link = manager.color_scheme_stylesheet_link_tag

      expect(link).to include("color_definitions_base")
    end

    it "loads base scheme when defined scheme id is missing" do
      link = manager.color_scheme_stylesheet_link_tag(125)
      expect(link).to include("color_definitions_base")
    end

    it "loads nothing when defined dark scheme id is missing" do
      link = manager.color_scheme_stylesheet_link_tag(125, "(prefers-color-scheme: dark)")
      expect(link).to eq("")
    end

    it "uses the correct color scheme from the default site theme" do
      cs = Fabricate(:color_scheme, name: 'Funky')
      theme = Fabricate(:theme, color_scheme_id: cs.id)
      SiteSetting.default_theme_id = theme.id

      link = manager.color_scheme_stylesheet_link_tag()
      expect(link).to include("/stylesheets/color_definitions_funky_#{cs.id}_")
    end

    it "uses the correct color scheme when a non-default theme is selected and it uses the base 'Light' scheme" do
      cs = Fabricate(:color_scheme, name: 'Not This')
      ColorSchemeRevisor.revise(cs, colors: [{ name: "primary", hex: "CC0000" }])
      default_theme = Fabricate(:theme, color_scheme_id: cs.id)
      SiteSetting.default_theme_id = default_theme.id

      user_theme = Fabricate(:theme, color_scheme_id: nil)

      link = manager(user_theme.id).color_scheme_stylesheet_link_tag(nil, "all")
      expect(link).to include("/stylesheets/color_definitions_base_")

      stylesheet = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: user_theme, manager: manager
      ).compile(force: true)

      expect(stylesheet).not_to include("--primary: #c00;")
      expect(stylesheet).to include("--primary: #222;") # from base scheme
    end

    it "uses the correct scheme when a valid scheme id is used" do
      link = manager.color_scheme_stylesheet_link_tag(ColorScheme.first.id)
      slug = Slug.for(ColorScheme.first.name) + "_" + ColorScheme.first.id.to_s
      expect(link).to include("/stylesheets/color_definitions_#{slug}_")
    end

    it "does not fail with a color scheme name containing spaces and special characters" do
      cs = Fabricate(:color_scheme, name: 'Funky Bunch -_ @#$*(')
      theme = Fabricate(:theme, color_scheme_id: cs.id)
      SiteSetting.default_theme_id = theme.id

      link = manager.color_scheme_stylesheet_link_tag
      expect(link).to include("/stylesheets/color_definitions_funky-bunch_#{cs.id}_")
    end

    it "updates outputted colors when updating a color scheme" do
      scheme = ColorScheme.create_from_base(name: "Neutral", base_scheme_id: "Neutral")
      theme = Fabricate(:theme)
      manager = manager(theme.id)

      builder = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )
      stylesheet = builder.compile

      ColorSchemeRevisor.revise(scheme, colors: [{ name: "primary", hex: "CC0000" }])

      builder2 = Stylesheet::Manager::Builder.new(
        target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
      )

      stylesheet2 = builder2.compile

      expect(stylesheet).not_to eq(stylesheet2)
      expect(stylesheet2).to include("--primary: #c00;")
    end

    it "includes updated font definitions" do
      details1 = manager.color_scheme_stylesheet_details(nil, "all")

      SiteSetting.base_font = DiscourseFonts.fonts[2][:key]

      details2 = manager.color_scheme_stylesheet_details(nil, "all")
      expect(details1[:new_href]).not_to eq(details2[:new_href])
    end

    context "theme colors" do
      let(:theme) { Fabricate(:theme).tap { |t|
        t.set_field(target: :common, name: "color_definitions", value: ':root {--special: rebeccapurple;}')
        t.save!
      }}
      let(:scss_child) { ':root {--child-definition: #{dark-light-choose(#c00, #fff)};}' }
      let(:child) { Fabricate(:theme, component: true, name: "Child Theme").tap { |t|
        t.set_field(target: :common, name: "color_definitions", value: scss_child)
        t.save!
      }}

      let(:scheme) { ColorScheme.base }
      let(:dark_scheme) { ColorScheme.create_from_base(name: 'Dark', base_scheme_id: 'Dark') }

      it "includes theme color definitions in color scheme" do
        manager = manager(theme.id)

        stylesheet = Stylesheet::Manager::Builder.new(
          target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
        ).compile(force: true)

        expect(stylesheet).to include("--special: rebeccapurple")
      end

      it "includes child color definitions in color schemes" do
        theme.add_relative_theme!(:child, child)
        theme.save!
        manager = manager(theme.id)

        stylesheet = Stylesheet::Manager::Builder.new(
          target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
        ).compile(force: true)

        expect(stylesheet).to include("--special: rebeccapurple")
        expect(stylesheet).to include("--child-definition: #c00")
      end

      it "respects selected color scheme in child color definitions" do
        theme.add_relative_theme!(:child, child)
        theme.save!

        manager = manager(theme.id)

        stylesheet = Stylesheet::Manager::Builder.new(
          target: :color_definitions, theme: theme, color_scheme: dark_scheme, manager: manager
        ).compile(force: true)

        expect(stylesheet).to include("--special: rebeccapurple")
        expect(stylesheet).to include("--child-definition: #fff")
      end

      it "fails gracefully for broken SCSS" do
        scss = "$test: $missing-var;"
        theme.set_field(target: :common, name: "color_definitions", value: scss)
        theme.save!

        manager = manager(theme.id)

        stylesheet = Stylesheet::Manager::Builder.new(
          target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
        )

        expect { stylesheet.compile }.not_to raise_error
      end

      it "child theme SCSS includes the default theme's color scheme variables" do
        SiteSetting.default_theme_id = theme.id
        custom_scheme = ColorScheme.create_from_base(name: "Neutral", base_scheme_id: "Neutral")
        ColorSchemeRevisor.revise(custom_scheme, colors: [{ name: "primary", hex: "CC0000" }])
        theme.color_scheme_id = custom_scheme.id
        theme.save!

        scss = "body{ border: 2px solid $primary;}"
        child.set_field(target: :common, name: "scss", value: scss)
        child.save!

        manager = manager(theme.id)

        child_theme_manager = Stylesheet::Manager::Builder.new(
          target: :desktop_theme, theme: child, manager: manager
        )

        child_theme_manager.compile(force: true)

        child_css = File.read(child_theme_manager.stylesheet_fullpath)
        expect(child_css).to include("body{border:2px solid #c00}")
      end
    end

    context 'encoded slugs' do
      before { SiteSetting.slug_generation_method = 'encoded' }
      after { SiteSetting.slug_generation_method = 'ascii' }

      it "strips unicode in color scheme stylesheet filenames" do
        cs = Fabricate(:color_scheme, name: 'Grün')
        cs2 = Fabricate(:color_scheme, name: '어두운')

        link = manager.color_scheme_stylesheet_link_tag(cs.id)
        expect(link).to include("/stylesheets/color_definitions_grun_#{cs.id}_")
        link2 = manager.color_scheme_stylesheet_link_tag(cs2.id)
        expect(link2).to include("/stylesheets/color_definitions_scheme_#{cs2.id}_")
      end
    end
  end

  describe ".precompile css" do
    before do
      class << STDERR
        alias_method :orig_write, :write
        def write(x)
        end
      end
    end

    after do
      class << STDERR
        def write(x)
          orig_write(x)
        end
      end
      FileUtils.rm_rf("tmp/stylesheet-cache")
    end

    it "correctly generates precompiled CSS" do
      scheme1 = ColorScheme.create!(name: "scheme1")
      scheme2 = ColorScheme.create!(name: "scheme2")
      core_targets = [:desktop, :mobile, :desktop_rtl, :mobile_rtl, :admin, :wizard]
      theme_targets = [:desktop_theme, :mobile_theme]

      Theme.update_all(user_selectable: false)
      user_theme = Fabricate(:theme, user_selectable: true, color_scheme: scheme1)
      default_theme = Fabricate(:theme, user_selectable: true, color_scheme: scheme2)

      child_theme = Fabricate(:theme).tap do |t|
        t.component = true
        t.save!
        user_theme.add_relative_theme!(:child, t)
      end

      child_theme_with_css = Fabricate(:theme).tap do |t|
        t.component = true

        t.set_field(
          target: :common,
          name: :scss,
          value: "body { background: green }"
        )

        t.save!

        user_theme.add_relative_theme!(:child, t)
        default_theme.add_relative_theme!(:child, t)
      end

      default_theme.set_default!

      StylesheetCache.destroy_all

      # only core
      output = capture_output(:stderr) do
        Stylesheet::Manager.precompile_css
      end

      results = StylesheetCache.pluck(:target)
      expect(results.size).to eq(core_targets.size)

      StylesheetCache.destroy_all

      # only themes
      output = capture_output(:stderr) do
        Stylesheet::Manager.precompile_theme_css
      end

      # Ensure we force compile each theme only once
      expect(output.scan(/#{child_theme_with_css.name}/).length).to eq(2)
      results = StylesheetCache.pluck(:target)
      expect(results.size).to eq(16) # (3 themes * 2 targets) + 10 color schemes (2 themes * 5 color schemes (4 defaults + 1 theme scheme))

      # themes + core
      Stylesheet::Manager.precompile_css
      results = StylesheetCache.pluck(:target)
      expect(results.size).to eq(22) # 6 core targets + 6 theme + 10 color schemes

      theme_targets.each do |tar|
        expect(results.count { |target| target =~ /^#{tar}_(#{user_theme.id}|#{default_theme.id})$/ }).to eq(2)
      end

      Theme.clear_default!
      StylesheetCache.destroy_all

      # themes + core with no theme set as default
      Stylesheet::Manager.precompile_css
      Stylesheet::Manager.precompile_theme_css
      results = StylesheetCache.pluck(:target)
      expect(results.size).to eq(22) # 6 core targets + 6 theme + 10 color schemes

      expect(results).to include("color_definitions_#{scheme1.name}_#{scheme1.id}_#{user_theme.id}")
      expect(results).to include("color_definitions_#{scheme2.name}_#{scheme2.id}_#{default_theme.id}")

      # Check that sourceMappingURL includes __ws parameter
      content = StylesheetCache.last.content
      expect(content).to match(/# sourceMappingURL=[^\/]+\.css\.map\?__ws=test\.localhost/)
    end
  end
end