diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..01d8f04 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +jobs: + css: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby: ['3.1', '3.4'] + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - run: bundle exec rake css diff --git a/Gemfile b/Gemfile index 8e6b92b..b02e4a8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,9 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in activeadmin_theme.gemspec +# Specify your gem's dependencies in active_admin_theme.gemspec gemspec + +group :development, :test do + gem 'rake' + gem 'sassc' +end diff --git a/Rakefile b/Rakefile index 809eb56..6ae6529 100644 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,8 @@ require "bundler/gem_tasks" +desc "Compile the theme against ActiveAdmin and check the result" +task :css do + ruby "test/css_check.rb" +end + +task default: :css diff --git a/app/assets/stylesheets/wigu/active_admin_theme.scss b/app/assets/stylesheets/wigu/active_admin_theme.scss index dbaf73a..9f783a9 100644 --- a/app/assets/stylesheets/wigu/active_admin_theme.scss +++ b/app/assets/stylesheets/wigu/active_admin_theme.scss @@ -5,6 +5,28 @@ $skinMainSecondColor: #5ea3d3!default; $skinBorderRadius: 4px!default; $skinBorderWindowColor: #e6e9ee!default; $skinTablePadding: 10px!default; + +// A wrong-typed override is legal SassScript, so it compiles without a word and +// emits CSS the browser drops — `border-bottom: none solid #5ea3d3`, +// `padding: 10 12px`. The rule silently vanishes and ActiveAdmin's own value +// reappears. Fail the build with a readable message instead. +@each $name, $value in ( + skinBorderRadius: $skinBorderRadius, + skinTablePadding: $skinTablePadding +) { + @if type-of($value) != number or (unitless($value) and $value != 0) { + @error "$#{$name} must be a length with a unit (e.g. 10px), got `#{$value}`."; + } +} +@each $name, $value in ( + skinMainFirstColor: $skinMainFirstColor, + skinMainSecondColor: $skinMainSecondColor, + skinBorderWindowColor: $skinBorderWindowColor +) { + @if type-of($value) != color { + @error "$#{$name} must be a color (use `transparent`, not `none`), got `#{$value}`."; + } +} //HTML----------------------------------------------------------------------------------------------------------------// html { diff --git a/test/css_check.rb b/test/css_check.rb new file mode 100644 index 0000000..76968d9 --- /dev/null +++ b/test/css_check.rb @@ -0,0 +1,73 @@ +# Compiles the theme against ActiveAdmin's own stylesheets and checks the +# result. Run with `rake css`. +# +# The gem ships SCSS, not CSS, so nothing in this repo's build ever compiles +# the theme: a change that only breaks under a particular variable override +# reaches consumers unnoticed. This is the only thing here that does compile it. +require "sassc" + +module CssCheck + STYLESHEETS = File.expand_path("../app/assets/stylesheets", __dir__) + THEME = "wigu/active_admin_theme" + + # Overrides a host project would plausibly write. Each must compile and keep + # producing CSS the browser accepts. + GOOD = { + "defaults" => "", + "accent" => '$skinMainSecondColor: #0066cc;', + "dark chrome" => '$skinMainFirstColor: #111418; $skinBorderWindowColor: #b8babe;', + "square + wide" => '$skinBorderRadius: 0; $skinTablePadding: 14px;', + "rem radius" => '$skinBorderRadius: 0.25rem;', + }.freeze + + # Wrong-typed overrides. All of these are legal SassScript, so without the + # guards in the theme they compile silently and emit declarations the browser + # drops — the rule simply vanishes and ActiveAdmin's own value reappears, with + # nothing in the build output to say why. + BAD = { + "$skinBorderRadius without a unit" => '$skinBorderRadius: 4;', + "$skinTablePadding: none" => '$skinTablePadding: none;', + "$skinBorderWindowColor: none" => '$skinBorderWindowColor: none;', + }.freeze + + def self.load_paths + activeadmin = Gem::Specification.find_by_name("activeadmin").gem_dir + [File.join(activeadmin, "app/assets/stylesheets"), STYLESHEETS] + end + + def self.compile(overrides) + source = <<~SCSS + @import "active_admin/mixins"; + #{overrides} + @import "active_admin/base"; + @import "#{THEME}"; + SCSS + SassC::Engine.new(source, load_paths: load_paths, style: :expanded).render + end + + def self.run + failures = [] + + GOOD.each do |name, overrides| + compile(overrides) + rescue SassC::SyntaxError => e + failures << "#{name}: should compile, but does not — #{e.message.lines.first.strip}" + end + + BAD.each do |name, overrides| + compile(overrides) + failures << "#{name}: should be rejected with @error, but compiled silently" + rescue SassC::SyntaxError + # expected — the theme's type guards caught it + end + + if failures.empty? + puts "css_check: #{GOOD.size} overrides compile clean, #{BAD.size} bad ones rejected" + else + failures.each { |failure| warn "css_check: #{failure}" } + abort "css_check: #{failures.size} problem(s)" + end + end +end + +CssCheck.run if $PROGRAM_NAME == __FILE__