diff --git a/.rubocop.yml b/.rubocop.yml index 78a5571..3d4c7cc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -6,7 +6,6 @@ AllCops: - 'spec/*_helper.rb' - 'Gemfile' - 'Rakefile' - - 'Vagrantfile' Documentation: Enabled: false \ No newline at end of file diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8830607..04b6c11 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,18 +1,34 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2015-10-29 13:19:55 -0500 using RuboCop version 0.34.1. +# on 2017-12-29 15:04:25 +0000 using RuboCop version 0.52.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 3 -# Configuration parameters: AllowURI, URISchemes. -Metrics/LineLength: - Max: 91 +# Offense count: 1 +Metrics/AbcSize: + Max: 19 # Offense count: 1 -# Configuration parameters: Exclude. -Style/FileName: +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 31 + +# Offense count: 1 +Naming/AccessorMethodName: + Exclude: + - 'spec/support/static_file_server.rb' + +# Offense count: 1 +# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. +# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS +Naming/FileName: Exclude: - 'lib/critical-path-css-rails.rb' + +# Offense count: 4 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 96 diff --git a/BACKLOG.md b/BACKLOG.md index 7f6b692..3713b97 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -1,12 +1,8 @@ # Backlog -## Tests -- Add a testing suite (preferably rspec) - ## Features - Allow the user to give a single route for a Controller#Show route, instead of hard coding every unique Resource#Show URL * Implementation should account for any route that allows variables/parameters in the URL - Error reporting during CSS generation (404, 500 errors, etc.) -- Allow the user to pass arguments to Penthouse.js, i.e. Viewport size, etc. For a list of the configurable options, please see [Penthouse](https://github.com/pocketjoso/penthouse) - Improve installation process, if possible - Improve implementation. Is their a better solution then using Rails.cache? \ No newline at end of file diff --git a/Gemfile b/Gemfile index 1cfb04f..7b9a526 100644 --- a/Gemfile +++ b/Gemfile @@ -3,8 +3,12 @@ source 'https://rubygems.org' gemspec group :development, :test do + gem 'actionpack' gem 'byebug', platform: [:ruby], require: false gem 'rubocop', require: false + gem 'rspec-rails', '~> 3.6' + gem 'capybara', '~> 2.16' + gem 'pry-rails' end # HACK: npm install on bundle diff --git a/README.md b/README.md index dc84efd..7d6d301 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,20 @@ rails generate critical_path_css:install Answer 'Y' when prompted to overwrite `critical_path_css.rake`. However, overwriting `critical_path_css.yml` is not necessary and not recommended. + +## Testing + +This gem is to be tested inside of docker/docker-compose. [Combustion](https://github.com/pat/combustion), alongside rspec-rails and capybara, are the primary components for testing. To run the test, you'll need to have [Docker](https://docs.docker.com/engine/installation) installed. Once installed, run the following commands in the gem's root to build, run, and shell into the docker container. + +```Bash + docker-compose build + docker-compose up -d + docker exec -it $(cat app_container_name) /bin/bash +``` + +Once shell'd in, run `bundle exec rspec spec` to run the test. The test rails app lives in `spec/internal`, and it can be viewed locally at `http://localhost:9292/` + + ## Versions The critical-path-css-rails gem follows these version guidelines: diff --git a/app_container_name b/app_container_name new file mode 100644 index 0000000..532a712 --- /dev/null +++ b/app_container_name @@ -0,0 +1 @@ +criticalpathcss_ruby \ No newline at end of file diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..4a1c95b --- /dev/null +++ b/config.ru @@ -0,0 +1,6 @@ +require 'rubygems' +require 'bundler' +require 'combustion' + +Combustion.initialize! :action_controller, :action_view +run Combustion::Application diff --git a/critical-path-css-rails.gemspec b/critical-path-css-rails.gemspec index ce7d39b..b66cabe 100644 --- a/critical-path-css-rails.gemspec +++ b/critical-path-css-rails.gemspec @@ -1,20 +1,20 @@ require File.expand_path('../lib/critical_path_css/rails/version', __FILE__) -Gem::Specification.new do |s| - s.name = 'critical-path-css-rails' - s.version = CriticalPathCSS::Rails::VERSION - s.platform = Gem::Platform::RUBY - s.authors = ['Michael Misshore'] - s.email = 'mmisshore@gmail.com' - s.summary = 'Critical Path CSS for Rails!' - s.description = 'Only load the CSS you need for the initial viewport in Rails!' - s.license = 'MIT' +Gem::Specification.new do |gem| + gem.name = 'critical-path-css-rails' + gem.version = CriticalPathCSS::Rails::VERSION + gem.platform = Gem::Platform::RUBY + gem.authors = ['Michael Misshore'] + gem.email = 'mmisshore@gmail.com' + gem.summary = 'Critical Path CSS for Rails!' + gem.description = 'Only load the CSS you need for the initial viewport in Rails!' + gem.license = 'MIT' - s.files = `git ls-files`.split("\n") - s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } - s.require_path = 'lib' + gem.files = `git ls-files`.split("\n") + gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } + gem.require_path = 'lib' - s.add_development_dependency 'rspec', '~> 3.6' + gem.add_development_dependency 'combustion', '~> 0.7.0' - s.extensions = ['ext/npm/extconf.rb'] + gem.extensions = ['ext/npm/extconf.rb'] end diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..da22674 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '2' +services: + ruby: + build: + context: . + dockerfile: docker/ruby/Dockerfile + ports: + - 9292:9292 + volumes: + - .:/app:rw + volumes_from: + - data + env_file: docker/ruby/.env + container_name: criticalpathcss_ruby + data: + build: + context: . + dockerfile: docker/ruby/Dockerfile + volumes: + - /gems + command: "true" \ No newline at end of file diff --git a/docker/ruby/.env b/docker/ruby/.env new file mode 100644 index 0000000..336ada1 --- /dev/null +++ b/docker/ruby/.env @@ -0,0 +1 @@ +RAILS_ENV=development diff --git a/docker/ruby/Dockerfile b/docker/ruby/Dockerfile new file mode 100644 index 0000000..27d32d9 --- /dev/null +++ b/docker/ruby/Dockerfile @@ -0,0 +1,18 @@ +FROM ruby:2.5.0 + +# Install Dependencies +RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - +RUN apt-get update && apt-get install -y build-essential libpq-dev nodejs npm + +RUN npm cache clean -f +RUN npm install -g n +RUN n 8.9.3 +RUN ln -sf /usr/local/n/versions/node/8.9.3/bin/node /usr/bin/nodejs + +ENV BUNDLE_PATH /gems + +WORKDIR /app + +COPY docker/ruby/startup.dev /usr/local/bin/startup +RUN chmod 755 /usr/local/bin/startup +CMD "/usr/local/bin/startup" \ No newline at end of file diff --git a/docker/ruby/startup.dev b/docker/ruby/startup.dev new file mode 100644 index 0000000..f9ddc5f --- /dev/null +++ b/docker/ruby/startup.dev @@ -0,0 +1,5 @@ +#!/bin/bash + +bundle check || bundle install + +bundle exec rackup --host 0.0.0.0 diff --git a/ext/npm/extconf.rb b/ext/npm/extconf.rb index 7dce95c..449c2c7 100644 --- a/ext/npm/extconf.rb +++ b/ext/npm/extconf.rb @@ -1,3 +1,2 @@ File.write 'Makefile', "make:\n\t\ninstall:\n\truby install.rb\nclean:\n\t\n" - diff --git a/lib/critical-path-css-rails.rb b/lib/critical-path-css-rails.rb index f4c431f..7586934 100644 --- a/lib/critical-path-css-rails.rb +++ b/lib/critical-path-css-rails.rb @@ -3,7 +3,7 @@ require 'critical_path_css/rails/config_loader' module CriticalPathCss - CACHE_NAMESPACE = 'critical-path-css' + CACHE_NAMESPACE = 'critical-path-css'.freeze def self.generate(route) ::Rails.cache.write( diff --git a/lib/critical_path_css/configuration.rb b/lib/critical_path_css/configuration.rb index 2e4b794..b46669c 100644 --- a/lib/critical_path_css/configuration.rb +++ b/lib/critical_path_css/configuration.rb @@ -1,7 +1,6 @@ require 'erb' module CriticalPathCss class Configuration - def initialize(config) @config = config end diff --git a/lib/critical_path_css/css_fetcher.rb b/lib/critical_path_css/css_fetcher.rb index bda7314..7812d2b 100644 --- a/lib/critical_path_css/css_fetcher.rb +++ b/lib/critical_path_css/css_fetcher.rb @@ -33,7 +33,7 @@ def css_for_route(route) # '^\.regexWorksToo' ], # ms; abort critical CSS generation after this timeout - 'timeout' => 30000, + 'timeout' => 30_000, # set to true to throw on CSS errors (will run faster if no errors) 'strict' => false, # characters; strip out inline base64 encoded resources larger than this diff --git a/lib/critical_path_css/rails/config_loader.rb b/lib/critical_path_css/rails/config_loader.rb index c9a6add..bbab1ba 100644 --- a/lib/critical_path_css/rails/config_loader.rb +++ b/lib/critical_path_css/rails/config_loader.rb @@ -1,10 +1,10 @@ module CriticalPathCss module Rails class ConfigLoader - CONFIGURATION_FILENAME = 'critical_path_css.yml' + CONFIGURATION_FILENAME = 'critical_path_css.yml'.freeze def load - config = YAML.load(ERB.new(File.read(configuration_file_path)).result)[::Rails.env] + config = YAML.safe_load(ERB.new(File.read(configuration_file_path)).result, [], [], true)[::Rails.env] config['css_path'] = "#{::Rails.root}/public" + ( config['css_path'] || ActionController::Base.helpers.stylesheet_path( diff --git a/lib/critical_path_css/rails/version.rb b/lib/critical_path_css/rails/version.rb index 885da23..8c99d20 100644 --- a/lib/critical_path_css/rails/version.rb +++ b/lib/critical_path_css/rails/version.rb @@ -1,5 +1,5 @@ module CriticalPathCSS module Rails - VERSION = '1.0.1' + VERSION = '1.0.1'.freeze end end diff --git a/lib/npm_commands.rb b/lib/npm_commands.rb index 5f50095..9d3d0e0 100644 --- a/lib/npm_commands.rb +++ b/lib/npm_commands.rb @@ -2,9 +2,8 @@ # NPM wrapper with helpful error messages class NpmCommands - # @return [Boolean] whether the installation succeeded - def install(*args) # rubocop:disable Metrics/MethodLength + def install(*args) return false unless check_nodejs_installed STDERR.puts 'Installing npm dependencies...' install_status = Dir.chdir File.expand_path('..', File.dirname(__FILE__)) do @@ -24,7 +23,7 @@ def install(*args) # rubocop:disable Metrics/MethodLength private - def check_nodejs_installed # rubocop:disable Metrics/MethodLength + def check_nodejs_installed return true if executable?('node') STDERR.puts( '-' * 60, diff --git a/spec/css_fetcher_spec.rb b/spec/css_fetcher_spec.rb deleted file mode 100644 index 0475c9a..0000000 --- a/spec/css_fetcher_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'CssFetcher' do - before :all do - StaticFileServer.start - end - - after :all do - StaticFileServer.stop - end - - it 'fetches css' do - config = CriticalPathCss::Configuration.new( - 'base_url' => StaticFileServer.url, - 'css_path' => 'spec/fixtures/static/test.css', - 'routes' => ['/test.html'] - ) - fetcher = CriticalPathCss::CssFetcher.new(config) - expect(fetcher.fetch).to( - eq('/test.html' => "p {\n color: red;\n}\n") - ) - end -end diff --git a/spec/features/generate_and_fetch_critical_css_spec.rb b/spec/features/generate_and_fetch_critical_css_spec.rb new file mode 100644 index 0000000..4c0aeba --- /dev/null +++ b/spec/features/generate_and_fetch_critical_css_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper.rb' + +RSpec.describe 'generate and fetch the critical css' do + before do + CriticalPathCss.generate_all + end + + context 'on the root page' do + let(:route) { '/' } + + it 'displays the correct critical CSS' do + visit route + expect(page).to have_content 'color: red;' + end + end +end diff --git a/spec/fixtures/static/test.html b/spec/fixtures/static/test.html deleted file mode 100644 index a16a45d..0000000 --- a/spec/fixtures/static/test.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - -

Hello world

- diff --git a/spec/internal/app/controllers/root_controller.rb b/spec/internal/app/controllers/root_controller.rb new file mode 100644 index 0000000..eb82de8 --- /dev/null +++ b/spec/internal/app/controllers/root_controller.rb @@ -0,0 +1,3 @@ +class RootController < ActionController::Base + def index; end +end diff --git a/spec/internal/app/views/layouts/application.html.erb b/spec/internal/app/views/layouts/application.html.erb new file mode 100644 index 0000000..a98b7c4 --- /dev/null +++ b/spec/internal/app/views/layouts/application.html.erb @@ -0,0 +1,4 @@ + + + <%= yield %> + diff --git a/spec/internal/app/views/root/index.html.erb b/spec/internal/app/views/root/index.html.erb new file mode 100644 index 0000000..62b320d --- /dev/null +++ b/spec/internal/app/views/root/index.html.erb @@ -0,0 +1 @@ +

<%= CriticalPathCss.fetch(request.path) %>

diff --git a/spec/internal/config/critical_path_css.yml b/spec/internal/config/critical_path_css.yml new file mode 100644 index 0000000..e40cc85 --- /dev/null +++ b/spec/internal/config/critical_path_css.yml @@ -0,0 +1,11 @@ +defaults: &defaults + base_url: http://0.0.0.0:9292 + css_path: /test.css + routes: + - / + +development: + <<: *defaults + +test: + <<: *defaults diff --git a/spec/internal/config/database.yml b/spec/internal/config/database.yml new file mode 100644 index 0000000..b978119 --- /dev/null +++ b/spec/internal/config/database.yml @@ -0,0 +1,3 @@ +test: + adapter: sqlite3 + database: db/combustion_test.sqlite diff --git a/spec/internal/config/routes.rb b/spec/internal/config/routes.rb new file mode 100644 index 0000000..d09aa7b --- /dev/null +++ b/spec/internal/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + root 'root#index' +end diff --git a/spec/internal/db/schema.rb b/spec/internal/db/schema.rb new file mode 100644 index 0000000..a4ab1bb --- /dev/null +++ b/spec/internal/db/schema.rb @@ -0,0 +1,3 @@ +ActiveRecord::Schema.define do + # +end diff --git a/spec/internal/log/.gitignore b/spec/internal/log/.gitignore new file mode 100644 index 0000000..bf0824e --- /dev/null +++ b/spec/internal/log/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/spec/internal/public/favicon.ico b/spec/internal/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/spec/fixtures/static/test.css b/spec/internal/public/test.css similarity index 90% rename from spec/fixtures/static/test.css rename to spec/internal/public/test.css index 3d9a2b2..e21e2c2 100644 --- a/spec/fixtures/static/test.css +++ b/spec/internal/public/test.css @@ -1,3 +1,3 @@ p { color: red; -} +} \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7b07287..6146035 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,11 +1,17 @@ # frozen_string_literal: true -require 'bundler/setup' -require 'critical-path-css-rails' +require 'bundler' -require 'support/static_file_server' +Bundler.require :default, :development + +Combustion.initialize! :action_controller, :action_view + +require 'rspec/rails' +require 'capybara/rails' RSpec.configure do |config| + config.use_transactional_fixtures = true + # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = '.rspec_status' diff --git a/spec/support/static_file_server.rb b/spec/support/static_file_server.rb deleted file mode 100644 index 345baa9..0000000 --- a/spec/support/static_file_server.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'socket' - -module StaticFileServer - class << self - def start # rubocop:disable Metrics/MethodLength,Metrics/AbcSize - @port = get_free_port - rd, wt = IO.pipe - @pid = fork do - require 'webrick' - rd.close - server = WEBrick::HTTPServer.new( - DocumentRoot: File.expand_path('spec/fixtures/static'), - Port: @port, - BindAddress: '127.0.0.1', - StartCallback: lambda do - # write "1", signal a server start message - wt.write(1) - wt.close - end - ) - trap('INT') { server.shutdown } - server.start - end - wt.close - # read a byte for the server start signal - rd.read(1) - rd.close - end - - def stop - Process.kill('INT', @pid) - end - - def url - "http://localhost:#{@port}" - end - - def get_free_port - server = TCPServer.new('127.0.0.1', 0) - port = server.addr[1] - server.close - port - end - end -end