diff --git a/.gitignore b/.gitignore index aed60f2..7869696 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ _site .sass-cache .jekyll-cache .jekyll-metadata +/tmp diff --git a/Gemfile b/Gemfile index 4e7367c..29177f5 100644 --- a/Gemfile +++ b/Gemfile @@ -6,4 +6,8 @@ gem "jekyll-seo-tag" group :test do gem "rspec" + gem "capybara" + gem "cuprite" + gem "rackup" + gem "webrick" end diff --git a/Gemfile.lock b/Gemfile.lock index 5f43e6f..8f96e31 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,14 +5,32 @@ GEM public_suffix (>= 2.0.2, < 8.0) base64 (0.3.0) bigdecimal (4.1.2) + capybara (3.40.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) colorator (1.1.0) concurrent-ruby (1.3.7) csv (3.3.5) + cuprite (0.17) + capybara (~> 3.0) + ferrum (~> 0.17.0) diff-lcs (1.6.2) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) eventmachine (1.2.7) + ferrum (0.17.2) + addressable (~> 2.5) + base64 (~> 0.2) + concurrent-ruby (~> 1.1) + webrick (~> 1.7) + websocket-driver (~> 0.7) ffi (1.17.4) ffi (1.17.4-arm64-darwin) ffi (1.17.4-x86_64-linux-gnu) @@ -65,14 +83,31 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) + matrix (0.4.3) mercenary (0.4.0) + mini_mime (1.1.5) + mini_portile2 (2.8.9) + nokogiri (1.19.4) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.19.4-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-gnu) + racc (~> 1.4) pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (7.0.5) + racc (1.8.1) + rack (3.2.6) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) rake (13.4.2) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) + regexp_parser (2.12.0) rexml (3.4.4) rouge (4.7.0) rspec (3.13.2) @@ -100,17 +135,28 @@ GEM unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.6.0) webrick (1.9.2) + websocket-driver (0.8.2) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) PLATFORMS arm64-darwin-21 arm64-darwin-22 + arm64-darwin-25 ruby x86_64-linux DEPENDENCIES + capybara + cuprite jekyll jekyll-seo-tag + rackup rspec + webrick RUBY VERSION ruby 4.0.2 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c80d44b..e58decc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,7 +13,69 @@ # it. # # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +require "capybara/rspec" +require "capybara/cuprite" +require "fileutils" +require "digest" + +ROOT_DIR = File.expand_path("..", __dir__) +SITE_DIR = File.join(ROOT_DIR, "tmp/system_test_site") +SITE_DIGEST_FILE = "#{SITE_DIR}.digest" + +# Directories, relative to ROOT_DIR, that never affect the built site and +# so are excluded when deciding whether a rebuild is needed. +SITE_SOURCE_EXCLUDE = %w[.git .github .jekyll-cache .sass-cache _site tmp spec vendor node_modules DOCS scripts .context].freeze + +# Fingerprints every file Jekyll might read (skipping the directories above), +# so we can tell whether the previously built site is still up to date. +def website_source_digest + files = Dir.glob(File.join(ROOT_DIR, "**/*"), File::FNM_DOTMATCH).select do |path| + next false if File.directory?(path) + + relative_top = path.delete_prefix("#{ROOT_DIR}/").split("/").first + !SITE_SOURCE_EXCLUDE.include?(relative_top) + end.sort + + Digest::SHA256.hexdigest(files.map { |f| "#{f}:#{File.mtime(f).to_f}:#{File.size(f)}" }.join) +end + +# Serves the built Jekyll site, resolving directory paths (e.g. "/about/") +# to their "index.html" the same way GitHub Pages does. +class SiteRackApp + def initialize(root) + @root = root + @files = Rack::Files.new(root) + end + + def call(env) + path = File.join(@root, env["PATH_INFO"]) + env = env.merge("PATH_INFO" => File.join(env["PATH_INFO"], "index.html")) if File.directory?(path) + @files.call(env) + end +end + +Capybara.javascript_driver = :cuprite +Capybara.default_driver = :cuprite +Capybara.server = :webrick +Capybara.app = SiteRackApp.new(SITE_DIR) + RSpec.configure do |config| + config.define_derived_metadata(file_path: %r{spec/system}) do |metadata| + metadata[:type] = :system + end + + config.before(:suite) do + next unless config.files_to_run.grep(%r{spec/system}).any? + + digest = website_source_digest + up_to_date = File.directory?(SITE_DIR) && File.exist?(SITE_DIGEST_FILE) && File.read(SITE_DIGEST_FILE) == digest + next if up_to_date + + FileUtils.rm_rf(SITE_DIR) + system({ "JEKYLL_ENV" => "test" }, "bundle exec jekyll build --destination #{SITE_DIR}", exception: true) + File.write(SITE_DIGEST_FILE, digest) + end + # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. diff --git a/spec/system/homepage_spec.rb b/spec/system/homepage_spec.rb new file mode 100644 index 0000000..aaf745a --- /dev/null +++ b/spec/system/homepage_spec.rb @@ -0,0 +1,10 @@ +require "spec_helper" + +RSpec.describe "Homepage" do + it "loads and shows the org name and member list" do + visit "/" + + expect(page).to have_content("Alliance of Civic Technologists") + expect(page).to have_content("Our Member Organizations") + end +end