diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index adeecdc..71b7867 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -7,7 +7,7 @@ on: - push - workflow_dispatch - deployment - - release + - release jobs: build: @@ -21,3 +21,14 @@ jobs: - name: Run rspec tests run: | bundle exec rspec + - name: summary + if: always() + run: ruby spec/support/spec_summary.rb + - name: Keep screenshots from failed tests + uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots + path: ${{ github.workspace }}/tmp/capybara + if-no-files-found: ignore + retention-days: 7 diff --git a/.rspec b/.rspec index c99d2e7..1841350 100644 --- a/.rspec +++ b/.rspec @@ -1 +1 @@ ---require spec_helper +-f documentation -f documentation --out tmp/rspec_output.txt -f json --out tmp/rspec_output.json -f html --out tmp/rspec_output.html diff --git a/Gemfile b/Gemfile index ead2e29..40cd3dd 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,8 @@ source 'https://rubygems.org' +gem 'jekyll', '~> 4' + gem 'faraday-retry', '~> 2.2' gem 'kramdown-parser-gfm' gem 'webrick' @@ -16,7 +18,8 @@ group :development, :test do gem 'axe-core-capybara' gem 'axe-core-rspec' gem 'capybara' - gem 'rack-jekyll', '>= 0.5.0' + gem 'capybara-screenshot' + gem 'rack' gem 'rackup' gem 'rspec' gem 'selenium-webdriver' diff --git a/Gemfile.lock b/Gemfile.lock index 45c2296..8d6ed00 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) ast (2.4.2) axe-core-api (4.9.1) dumb_delegator @@ -29,10 +29,15 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + capybara-screenshot (1.0.26) + capybara (>= 1.0, < 4) + launchy + childprocess (5.1.0) + logger (~> 1.5) coercible (1.0.0) descendants_tracker (~> 0.0.1) colorator (1.1.0) - concurrent-ruby (1.3.3) + concurrent-ruby (1.3.4) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.5.1) @@ -52,13 +57,13 @@ GEM ffi (1.17.0-x86_64-darwin) ffi (1.17.0-x86_64-linux-gnu) forwardable-extended (2.6.0) - google-protobuf (4.27.1-arm64-darwin) + google-protobuf (4.27.3-arm64-darwin) bigdecimal rake (>= 13) - google-protobuf (4.27.1-x86_64-darwin) + google-protobuf (4.27.3-x86_64-darwin) bigdecimal rake (>= 13) - google-protobuf (4.27.1-x86_64-linux) + google-protobuf (4.27.3-x86_64-linux) bigdecimal rake (>= 13) http_parser.rb (0.8.0) @@ -95,7 +100,7 @@ GEM jekyll-watch (2.2.1) listen (~> 3.0) json (2.7.2) - just-the-docs (0.8.2) + just-the-docs (0.9.0) jekyll (>= 3.8.5) jekyll-include-cache jekyll-seo-tag (>= 2.0) @@ -105,6 +110,9 @@ GEM kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) language_server-protocol (3.17.0.3) + launchy (3.0.1) + addressable (~> 2.8) + childprocess (~> 5.0) liquid (4.0.4) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) @@ -115,40 +123,36 @@ GEM mini_mime (1.1.5) net-http (0.4.1) uri - nokogiri (1.16.6-arm64-darwin) + nokogiri (1.16.7-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.6-x86_64-darwin) + nokogiri (1.16.7-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.6-x86_64-linux) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) octokit (6.1.1) faraday (>= 1, < 3) sawyer (~> 0.9) - parallel (1.25.1) - parser (3.3.3.0) + parallel (1.26.2) + parser (3.3.4.2) ast (~> 2.4.1) racc pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (5.1.1) - racc (1.8.0) - rack (1.6.13) - rack-jekyll (0.5.0) - jekyll (>= 1.3) - listen (>= 1.3) - rack (~> 1.5) + public_suffix (6.0.1) + racc (1.8.1) + rack (3.1.7) rack-test (2.1.0) rack (>= 1.3) - rackup (1.0.0) - rack (< 3) - webrick + rackup (2.1.0) + rack (>= 3) + webrick (~> 1.8) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) regexp_parser (2.9.2) - rexml (3.3.0) + rexml (3.3.5) strscan rouge (4.3.0) rspec (3.13.0) @@ -164,37 +168,38 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) - rubocop (1.64.1) + rubocop (1.65.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) + regexp_parser (>= 2.4, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.3) + rubocop-ast (1.32.0) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) - rubocop-rspec (3.0.1) + rubocop-rspec (3.0.4) rubocop (~> 1.61) ruby-progressbar (1.13.0) rubyzip (2.3.2) safe_yaml (1.0.5) - sass-embedded (1.77.5-arm64-darwin) - google-protobuf (>= 3.25, < 5.0) - sass-embedded (1.77.5-x86_64-darwin) - google-protobuf (>= 3.25, < 5.0) - sass-embedded (1.77.5-x86_64-linux-gnu) - google-protobuf (>= 3.25, < 5.0) + sass-embedded (1.77.8-arm64-darwin) + google-protobuf (~> 4.26) + sass-embedded (1.77.8-x86_64-darwin) + google-protobuf (~> 4.26) + sass-embedded (1.77.8-x86_64-linux-gnu) + google-protobuf (~> 4.26) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - selenium-webdriver (4.21.1) + selenium-webdriver (4.23.0) base64 (~> 0.2) + logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -209,7 +214,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) webrick (1.8.1) - websocket (1.2.10) + websocket (1.2.11) xpath (3.2.0) nokogiri (~> 1.8) @@ -222,12 +227,14 @@ DEPENDENCIES axe-core-capybara axe-core-rspec capybara + capybara-screenshot faraday-retry (~> 2.2) + jekyll (~> 4) jekyll-github-metadata (~> 2.16) jekyll-sitemap just-the-docs kramdown-parser-gfm - rack-jekyll (>= 0.5.0) + rack rackup rspec rubocop diff --git a/_config.yml b/_config.yml index 46922fb..eb89c28 100644 --- a/_config.yml +++ b/_config.yml @@ -21,24 +21,14 @@ tagline: A Jekyll template for course websites description: A modern, highly customizable, responsive Jekyll template for course websites # TODO(template): this should be built from the staff list... author: Various Bears +# You should use either light or dark as the theme. +color_scheme: light # TODO(setup): Set this to the semester, e.g. /sp24, (faXX / spXX / suXX / wiXX ) baseurl: '/berkeley-class-site' # the subpath of your site, which should just be the semester. # TODO(setup): Set this to your course's domain url: 'https://berkeley-eecs.github.io' # the hostname & protocol for your site, e.g. http://example.com -# TODO(setup): This should be one of eecs, dsus, stats -# This controls some footer text, and later custom styling. -course_department: dsus - -# The sitemap **must** be enabled. It's published as /baseurl/sitemap.xml -plugins: - - jekyll-sitemap - - jekyll-seo-tag - - jekyll-github-metadata - - jekyll-include-cache - - # Course variables # Course variables can appear in various places around the website, if something isn't used leave it blank. # TODO(setup): Set all of these course variables @@ -48,6 +38,9 @@ gradescope_course_id: 123456 # you can find this in the Gradescope URL after /co bcourses_course_id: 123456 # Same as above, but for bCourses. Leave blank if not in use... ed_course_id: 123456 # Again, same as above. sememster: spYY | suYY | faYY # set for the current seemester +# This should be one of eecs, dsus, stat +# (Future) This will control some footer text, and later custom styling. +course_department: dsus # This should be the page of all class archives # Typically just / for DS courses (with a visible index page), or /archives if you're hosting your own, or a link to the inst.eecs page # If you have no archive page, comment this line out or leave blank. @@ -56,7 +49,7 @@ class_archive_path: / # TODO(setup): Set these auxiliary links as you wish - they show up on the top right aux_links: # TODO(template): Move this to be built-in. - Ed: + Ed: - 'https://edstem.org' OH Queue: - 'https://oh.c88c.org' @@ -129,10 +122,23 @@ defaults: # Just the Docs / Berkeley Class Site Common Config # Options below here will likely not need to be configured.. ###################################################################### +# TODO(template): CSS compilations. Handle warnings... +sass: + style: expanded + sass_dir: _sass + sourcemap: always + quiet_deps: true + verbose: false + +# The sitemap **must** be enabled. It's published as /baseurl/sitemap.xml +plugins: + - jekyll-sitemap + - jekyll-seo-tag + - jekyll-github-metadata + - jekyll-include-cache # Theme settings theme: just-the-docs -color_scheme: light search_enabled: true heading_anchors: true permalink: pretty @@ -158,7 +164,7 @@ compress_html: profile: false # Native Jeykll build options -# You likely shouldn't need to touch these, but they are documented here just in case. +# You likely shouldn't need to touch these, but they are documented here just in case. # https://jekyllrb.com/docs/configuration/options/ # This makes it easier when pushing to a static directory. @@ -172,7 +178,8 @@ timezone: America/Los_Angeles include: - LICENSE - .htaccess - + - robots.txt + exclude: - .sass-cache/ - .jekyll-cache/ diff --git a/_includes/footer_custom.html b/_includes/footer_custom.html index 8e86341..6aba45d 100644 --- a/_includes/footer_custom.html +++ b/_includes/footer_custom.html @@ -13,10 +13,10 @@

-

+

Copyright ©{{ "now" | date: "%Y" }}, Regents of the University of Californa and respective authors.

-

+

This site is built following the Berkeley Class Site template, which is generously based on the Just the Class , and Just the Docs templates.

diff --git a/_includes/head_custom.html b/_includes/head_custom.html index ce29aa5..2e7aafa 100644 --- a/_includes/head_custom.html +++ b/_includes/head_custom.html @@ -2,5 +2,24 @@ - + + + diff --git a/_includes/nav_footer_custom.html b/_includes/nav_footer_custom.html index 0dcb58e..396860b 100644 --- a/_includes/nav_footer_custom.html +++ b/_includes/nav_footer_custom.html @@ -1,9 +1,9 @@ {% if site.class_archive_path %} - + {% endif %} diff --git a/_plugins/config_validator.rb b/_plugins/config_validator.rb index 06f832a..d49aa59 100644 --- a/_plugins/config_validator.rb +++ b/_plugins/config_validator.rb @@ -5,14 +5,17 @@ # 1) Ensures required attributes are present in the config file. # 2) Ensures the baseurl is a consistent format based on the semester # 3) Uses a `course_department` config to implement some shared styling/language. -# Future config validations might make sense, like footer/a11y/etc. +# 4) Ensures the `color_scheme` is valid. +# Future config validations might make sense, like footer/a11y/etc. configuration, # Implement additional validations by registering an entry in `KEY_VALIDATIONS` # This is a key_name => function_name map. # function_name should be defined in the ConfigValidator class, and is called with -# the *value* of the config. +# the key, value pair in the config. # Note that Jekyll parses the YAML file such that the config keys are *strings* not symbols. +# See the inclusion_validator to add simple allow-list style validations + # A simple class for nicer error message formatting. class ConfigValidationError < StandardError attr_reader :errors @@ -31,11 +34,17 @@ module Jekyll # Jekyll::ConfigValidator class definition (see docs at the top of file) class ConfigValidator SEMESTER_REGEXP = /(wi|sp|su|fa)\d\d$/ - VALID_DEPTS = %w[eecs dsus stat].freeze + VALID_COURSE_DEPARTMENT = %w[eecs dsus stat].freeze + VALID_COLOR_SCHEME = %w[light dark].freeze + + # To validate a key with an 'allow list': + # Use the function :inclusion_validator + # and definite VALID_KEY_NAME above KEY_VALIDATIONS = { url: :validate_clean_url, baseurl: :validate_semester_format, - course_department: :validate_department + course_department: :inclusion_validator, + color_scheme: :inclusion_validator }.freeze attr_accessor :config, :errors @@ -49,7 +58,7 @@ def validate validate_keys! KEY_VALIDATIONS.each do |key, validator| - send(validator, config[key.to_s]) if @config.key?(key.to_s) + send(validator, key, config[key.to_s]) if @config.key?(key.to_s) end raise ConfigValidationError, errors if errors.length.positive? @@ -66,12 +75,12 @@ def validate_keys! private - def validate_clean_url(url) + def validate_clean_url(_key, url) errors << '`url` should not end with a `/`' if url.end_with?('/') errors << '`url` should contain a protocol' unless url.match?(%r{https?://}) end - def validate_semester_format(baseurl) + def validate_semester_format(_key, baseurl) # This is just for consistency of URL presentation. errors << '`baseurl` must start with a `/`.' unless baseurl.match?(%r{^/}) # skip, just for the template. @@ -82,8 +91,9 @@ def validate_semester_format(baseurl) errors << "`baseurl` must be a valid semester (faXX, spXX, suXX or wiXX), not #{baseurl}" end - def validate_department(dept) - errors << "`course_department` must be one of #{VALID_DEPTS} (not '#{dept}')" unless VALID_DEPTS.include?(dept) + def inclusion_validator(key, value) + allowed = self.class.const_get("VALID_#{key.upcase}") + errors << "`#{key}` must be one of #{allowed} (not '#{value}')" unless allowed.include?(value) end end end diff --git a/_sass/berkeley/berkeley.scss b/_sass/berkeley/berkeley.scss new file mode 100644 index 0000000..5284780 --- /dev/null +++ b/_sass/berkeley/berkeley.scss @@ -0,0 +1,101 @@ +// This config applies to all berkeley courses. +// It is loaded *after* all Just-the-class customizations + +// UC Berkeley fonts 'Inter' (sans serif) 'Source Serif' (serif), and 'Source Code Pro' (monospace) +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&display=swap'); + +// Override default JTD CSS for larger/more accessibile fonts. +html { + font-size: 16px; +} + +body { + font-family: $body-font-family; + font-size: inherit; + line-height: $body-line-height; + color: $body-text-color; + background-color: $body-background-color; + overflow-wrap: break-word; +} + +// Link Colors +a, +.main-content .anchor-heading svg { + color: $link-color; + + &:hover, + &:focus { + color: $hover-focus-link-color; + } +} + +a:not([class]):hover { + text-decoration-color: lighten($link-color, 20%); +} + +h1, h2, h3, h4, h5, h6, +.serif { + font-family: $serif-font-family; +} + +// Override JTD to be darker +.label-green:not(g) { + background-color: $green-300; +} + +.label-red:not(g) { + background-color: $red-300; +} + +code { + padding: 0.2em 0.4em; + border: none; +} + +iframe { + max-width: 100%; +} + +details { + @extend .mb-4; +} + +summary { + @extend .btn, .btn-outline; + + width: 100%; +} + +.main-content-wrap { + max-width: $content-width; + margin: auto; +} + +.main-content { + dl { + display: block; + grid-template-columns: none; + } + + dt { + font-weight: 700; + text-align: start; + + &::after { + content: normal; + } + } + + dd { + font-weight: normal; + + + dt { + margin-top: 1em; + } + } +} + +// This is intended to be used for the footer +.text-lighter { + color: $body-lighter-color; +} diff --git a/_sass/berkeley/variables.scss b/_sass/berkeley/variables.scss new file mode 100644 index 0000000..366f259 --- /dev/null +++ b/_sass/berkeley/variables.scss @@ -0,0 +1,28 @@ +// Common Berkeley Variables. +// This file is explicitly included before berkeley.scss in setup.scss +// Some colors may be override by berkeley_light or berkeley_dark + +$content-width: 1200px; + +// Typography +$mono-font-family: "Source Code Pro", "SFMono-Regular", menlo, consolas, monospace !default; +$body-font-family: 'Inter', system-ui, -apple-system, blinkmacsystemfont, "Segoe UI", +roboto, "Helvetica Neue", arial, sans-serif, "Segoe UI Emoji" !default; +$serif-font-family: 'Source Serif 4', 'Georiga', 'Baskerville', serif; + +$berkeley-blue-old: #003262; +$berkeley-blue: #002676; // rgb(0, 38, 118); + +// Colors +$link-color: $berkeley-blue; +$hover-focus-link-color: lighten($link-color, 20%); + +// Layout +$gutter-spacing: $sp-6; +$gutter-spacing-sm: $sp-4; +$nav-width: 256px; + +// Components +$box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07), 0 4px 14px rgba(0, 0, 0, 0.05); +$module-date-color: $link-color; +$staffer-image-size: 100px; diff --git a/_sass/color_schemes/dark.scss b/_sass/color_schemes/dark.scss new file mode 100644 index 0000000..f5b6df6 --- /dev/null +++ b/_sass/color_schemes/dark.scss @@ -0,0 +1,49 @@ +// This file is coped from Just The Docs +// Individual courses should not modify it! +// All deviations should be documented. +// https://github.com/just-the-docs/just-the-docs/blob/main/_sass/color_schemes/light.scss + +/////////// Do not modify this section. +$color-scheme: dark; +$body-background-color: $grey-dk-300; +$body-heading-color: $grey-lt-000; +$body-text-color: $grey-lt-300; +$link-color: $blue-000; +$nav-child-link-color: $grey-dk-000; +$sidebar-color: $grey-dk-300; +$base-button-color: $grey-dk-250; +$btn-primary-color: $blue-200; +$code-background-color: #31343f; // OneDarkJekyll default for syntax-one-dark-vivid +$code-linenumber-color: #dee2f7; // OneDarkJekyll .nf for syntax-one-dark-vivid +$feedback-color: darken($sidebar-color, 3%); +$table-background-color: $grey-dk-250; +$search-background-color: $grey-dk-250; +$search-result-preview-color: $grey-dk-000; +$border-color: $grey-dk-200; +/////////////////// Add modifications below + +@import '../snytax_highliters/a11y-dark'; + +// Override dark-mode variables here +$grey-dk-400: #0C0A0E !default; +$body-background-color: $grey-dk-400; +$sidebar-color: $grey-dk-400; +$search-background-color: $grey-dk-400; +$body-text-color: $white; +$body-lighter-color: darken($body-text-color, 20%); + +$link-color: #406FFE; +$hover-focus-link-color: darken($link-color, 10%); + +// TODO(template): We need to figure out a much better solution! +// This swaps the "dk" and "lt" variants from the original JTD files. +$grey-lt-000: #959396 !default; +$grey-lt-100: #5c5962 !default; +$grey-lt-200: #44434d !default; +$grey-lt-250: #302d36 !default; +$grey-lt-300: #27262b !default; + +$grey-dk-000: #f5f6fa !default; +$grey-dk-100: #eeebee !default; +$grey-dk-200: #ecebed !default; +$grey-dk-300: #e6e1e8 !default; diff --git a/_sass/color_schemes/light.scss b/_sass/color_schemes/light.scss new file mode 100644 index 0000000..5b0a71d --- /dev/null +++ b/_sass/color_schemes/light.scss @@ -0,0 +1,26 @@ +// This file is coped from Just The Docs +// Individual courses should not modify it! +// All deviations should be documented. +// https://github.com/just-the-docs/just-the-docs/blob/main/_sass/color_schemes/light.scss + +/////////// Do not modify this section. +$color-scheme: light !default; +$body-background-color: $white !default; +$body-heading-color: $grey-dk-300 !default; +$body-text-color: $grey-dk-100 !default; +$link-color: $purple-000 !default; +$nav-child-link-color: $grey-dk-100 !default; +$sidebar-color: $grey-lt-000 !default; +$base-button-color: #f7f7f7 !default; +$btn-primary-color: $purple-100 !default; +$code-background-color: $grey-lt-000 !default; +$feedback-color: darken($sidebar-color, 3%) !default; +$table-background-color: $white !default; +$search-background-color: $white !default; +$search-result-preview-color: $grey-dk-000 !default; +/////////////////// Add modifications below + +@import "../snytax_highliters/a11y-light"; + +// Minimum lightness on white. +$body-lighter-color: $grey-dk-100; diff --git a/_sass/custom/course_overrides.scss b/_sass/custom/course_overrides.scss new file mode 100644 index 0000000..ba372e2 --- /dev/null +++ b/_sass/custom/course_overrides.scss @@ -0,0 +1,2 @@ +// Put your custom CSS here! +// You an reference all variables in the Berkeley files diff --git a/_sass/custom/custom.scss b/_sass/custom/custom.scss index f028758..f2cca91 100644 --- a/_sass/custom/custom.scss +++ b/_sass/custom/custom.scss @@ -1,62 +1,12 @@ -// Just the Class dependencies -@import 'card'; - -// Just the Class styles -@import 'announcement'; -@import 'module'; -@import 'schedule'; -@import 'staffer'; - -// Overrides -code { - font-size: 14px; - padding: 0.2em 0.4em; - border: none; -} - -iframe { - max-width: 100%; -} - -details { - @extend .mb-4; -} - -summary { - @extend .btn, .btn-outline; - - width: 100%; -} - -.label-a11y-green:not(g) { - background-color: $green-300; -} - -.main-content-wrap { - max-width: $content-width; - margin: auto; -} - -.main-content { - dl { - display: block; - grid-template-columns: none; - } - - dt { - font-weight: 700; - text-align: start; - - &::after { - content: normal; - } - } - - dd { - font-weight: normal; - - + dt { - margin-top: 1em; - } - } -} +// You may want to add CSS in additional files. +// It's best to not edit this file too much. +// "course_overrides.scss" is designed for each course's customizations. +// You can make additional files as necessary. +// You would then add `@import "file"; to this file. + +// Just the Class customizations are all loaded in one file. +@import '../just-the-class/just-the-class'; +@import '../berkeley/berkeley'; + +// Place all of your course-specific CSS here. +@import './course_overrides'; diff --git a/_sass/custom/setup.scss b/_sass/custom/setup.scss new file mode 100644 index 0000000..e39267c --- /dev/null +++ b/_sass/custom/setup.scss @@ -0,0 +1,7 @@ +// Use this file to customize SASS variables. +// You will likely want to review the Jst the Docs CSS +// This file is loaded *early* in the SASS compilation process. + +// Load berkeley variable customizations +// These are loaded before berkeley_light and berkeley_dark themes +@import '../berkeley/variables'; diff --git a/_sass/custom/announcement.scss b/_sass/just-the-class/announcement.scss similarity index 100% rename from _sass/custom/announcement.scss rename to _sass/just-the-class/announcement.scss diff --git a/_sass/custom/card.scss b/_sass/just-the-class/card.scss similarity index 100% rename from _sass/custom/card.scss rename to _sass/just-the-class/card.scss diff --git a/_sass/just-the-class/just-the-class.scss b/_sass/just-the-class/just-the-class.scss new file mode 100644 index 0000000..dc5e2be --- /dev/null +++ b/_sass/just-the-class/just-the-class.scss @@ -0,0 +1,12 @@ +// This file is common to Just The Class / theme styles. +// You should not add CSS directly to this file. +// You probably want to add files to _sass/custom/custom.scss + +// Just the Class dependencies +@import 'card'; + +// Just the Class styles +@import 'announcement'; +@import 'module'; +@import 'schedule'; +@import 'staffer'; diff --git a/_sass/custom/lab.scss b/_sass/just-the-class/lab.scss similarity index 100% rename from _sass/custom/lab.scss rename to _sass/just-the-class/lab.scss diff --git a/_sass/custom/module.scss b/_sass/just-the-class/module.scss similarity index 100% rename from _sass/custom/module.scss rename to _sass/just-the-class/module.scss diff --git a/_sass/custom/schedule.scss b/_sass/just-the-class/schedule.scss similarity index 89% rename from _sass/custom/schedule.scss rename to _sass/just-the-class/schedule.scss index f059310..da5ce21 100644 --- a/_sass/custom/schedule.scss +++ b/_sass/just-the-class/schedule.scss @@ -22,7 +22,7 @@ .schedule-time { @extend .fs-2; - color: $grey-dk-000; + color: $grey-dk-200; height: 40px; margin: 0; padding: $sp-2; @@ -93,15 +93,16 @@ } &.lecture { - background-color: $grey-dk-000; + background-color: $grey-lt-300; + color: $grey-dk-300; } &.section { - background-color: $purple-000; + background-color: darken($purple-000, 15%); } &.office-hours { - background-color: $blue-000; + background-color: darken($green-200, 10%); } } } diff --git a/_sass/custom/staffer.scss b/_sass/just-the-class/staffer.scss similarity index 56% rename from _sass/custom/staffer.scss rename to _sass/just-the-class/staffer.scss index 988a721..0e8f959 100644 --- a/_sass/custom/staffer.scss +++ b/_sass/just-the-class/staffer.scss @@ -12,15 +12,22 @@ p, .staffer-name { margin: $sp-1 !important; + + .anchor-heading { + width: 1.8rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + } } .staffer-badge { - @extend .label, .text-grey-dk-100, .bg-grey-lt-200; + @extend .label, .text-grey-dk-300, .bg-grey-lt-200; + -webkit-user-select: none; user-select: none; } .staffer-meta { - @extend .text-grey-dk-000; + @extend .text-grey-dk-300; } } diff --git a/_sass/snytax_highliters/a11y-dark.css b/_sass/snytax_highliters/a11y-dark.css new file mode 100644 index 0000000..20f3214 --- /dev/null +++ b/_sass/snytax_highliters/a11y-dark.css @@ -0,0 +1,136 @@ +/* a11y-dark theme */ +/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ +/* @author: ericwbailey */ +/* Adapted to pygments/rouge by @cycomachead */ + +:root { + --pygments-a11y-bg-color: #2b2b2b; + --pygments-a11y-dark-on-light: #000; /* Used for errors */ + --pygments-a11y-light-gray: #d4d0ab; /* comments */ + --pygments-a11y-dark-gray: #f8f8f2; + --pygments-a11y-purple: #dcc6e0; + --pygments-a11y-blue: #00e0e0; + --pygments-a11y-green: #abe338; + --pygments-a11y-yellow: #ffd700; /* attributes */ + --pygments-a11y-orange: #f5ab35; + --pygments-a11y-red: #ffa07a; +} + +.highlight, +pre.highlight { + background: var(--pygments-a11y-bg-color); + color: var(--pygments-a11y-dark-gray); +} + +.highlight pre, +.highlight .hll, +.highlight .w { + background: var(--pygments-a11y-bg-color); +} + +.highlight .c { + color: var(--pygments-a11y-light-gray); + font-style: italic; +} + +.highlight .err { + color: var(--pygments-a11y-dark-on-light); + background-color: var(--pygments-a11y-red); +} + +.highlight .k, +.highlight .kc, +.highlight .kd, +.highlight .kn, +.highlight .kp, +.highlight .kr, +.highlight .kt { + color: var(--pygments-a11y-purple); +} + +.highlight .n, +.highlight .o, +.highlight .p, +.highlight .nf, +.highlight .nn, +.highlight .nx { + color: var(--pygments-a11y-dark-gray); +} + +.highlight .cm, +.highlight .cp, +.highlight .c1, +.highlight .cs { + color: var(--pygments-a11y-light-gray); + font-style: italic; +} + +.highlight .ge { + font-style: italic; +} + +.highlight .gs, +.highlight .ow { + font-weight: 700; +} + +.highlight .l, +.highlight .ld, +.highlight .s, +.highlight .sb, +.highlight .sc, +.highlight .sd, +.highlight .s2, +.highlight .se, +.highlight .sh, +.highlight .si, +.highlight .sx, +.highlight .s1 { + color: var(--pygments-a11y-green); +} + +.highlight .bp, +.highlight .nb, +.highlight .nc, +.highlight .no, +.highlight .nd, +.highlight .ni, +.highlight .ne, +.highlight .nl, +.highlight .nv, +.highlight .py, +.highlight .vc, +.highlight .vg { + color: var(--pygments-a11y-orange); /* #ca7601; */ +} + +.highlight .gd, +.highlight .nt, +.highlight .vi, +.highlight .language-json .w + .s2 { + color: var(--pygments-a11y-red); /* #e35549 */ +} + +.highlight .il, +.highlight .m, +.highlight .mf, +.highlight .mh, +.highlight .mi, +.highlight .mo, +.highlight .na { + color: var(--pygments-a11y-yellow); /* #b66a00 */ +} + +.highlight .sr, +.highlight .ss, +.highlight .language-json .kc { + color: var(--pygments-a11y-blue); /*#0083bb */ +} + +.highlight .gu { + color: var(--pygments-a11y-light-gray); /* was #75715e kind of dark yellow */ +} + +.highlight .gi { + color: var(--pygments-a11y-green); /* was #43d089 seafoam ish */ +} diff --git a/_sass/snytax_highliters/a11y-light.css b/_sass/snytax_highliters/a11y-light.css new file mode 100644 index 0000000..fc95d77 --- /dev/null +++ b/_sass/snytax_highliters/a11y-light.css @@ -0,0 +1,136 @@ +/* a11y-light theme */ +/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ +/* @author: ericwbailey */ +/* Adapted to pygments/rouge by @cycomachead */ + +:root { + --pygments-a11y-bg-color: #fefefe; + --pygments-a11y-light-on-dark: #FFF; /* Used for errors */ + --pygments-a11y-light-gray: #696969; /* comments */ + --pygments-a11y-dark-gray: #545454; + --pygments-a11y-purple: #7928a1; + --pygments-a11y-blue: #007faa; + --pygments-a11y-green: #008000; + --pygments-a11y-yellow: #aa5d00; /* attributes */ + --pygments-a11y-orange: #aa5d00; + --pygments-a11y-red: #d91e18; +} + +.highlight, +pre.highlight { + background: var(--pygments-a11y-bg-color); + color: var(--pygments-a11y-dark-gray); +} + +.highlight pre, +.highlight .hll, +.highlight .w { + background: var(--pygments-a11y-bg-color); +} + +.highlight .c { + color: var(--pygments-a11y-light-gray); + font-style: italic; +} + +.highlight .err { + color: var(--pygments-a11y-light-on-dark); + background-color: var(--pygments-a11y-red); +} + +.highlight .k, +.highlight .kc, +.highlight .kd, +.highlight .kn, +.highlight .kp, +.highlight .kr, +.highlight .kt { + color: var(--pygments-a11y-purple); +} + +.highlight .n, +.highlight .o, +.highlight .p, +.highlight .nf, +.highlight .nn, +.highlight .nx { + color: var(--pygments-a11y-dark-gray); +} + +.highlight .cm, +.highlight .cp, +.highlight .c1, +.highlight .cs { + color: var(--pygments-a11y-light-gray); + font-style: italic; +} + +.highlight .ge { + font-style: italic; +} + +.highlight .gs, +.highlight .ow { + font-weight: 700; +} + +.highlight .l, +.highlight .ld, +.highlight .s, +.highlight .sb, +.highlight .sc, +.highlight .sd, +.highlight .s2, +.highlight .se, +.highlight .sh, +.highlight .si, +.highlight .sx, +.highlight .s1 { + color: var(--pygments-a11y-green); +} + +.highlight .bp, +.highlight .nb, +.highlight .nc, +.highlight .no, +.highlight .nd, +.highlight .ni, +.highlight .ne, +.highlight .nl, +.highlight .nv, +.highlight .py, +.highlight .vc, +.highlight .vg { + color: var(--pygments-a11y-orange); /* #ca7601; */ +} + +.highlight .gd, +.highlight .nt, +.highlight .vi, +.highlight .language-json .w + .s2 { + color: var(--pygments-a11y-red); /* #e35549 */ +} + +.highlight .il, +.highlight .m, +.highlight .mf, +.highlight .mh, +.highlight .mi, +.highlight .mo, +.highlight .na { + color: var(--pygments-a11y-yellow); /* #b66a00 */ +} + +.highlight .sr, +.highlight .ss, +.highlight .language-json .kc { + color: var(--pygments-a11y-blue); /*#0083bb */ +} + +.highlight .gu { + color: var(--pygments-a11y-light-gray); /* was #75715e kind of dark yellow */ +} + +.highlight .gi { + color: var(--pygments-a11y-green); /* was #43d089 seafoam ish */ +} diff --git a/_sass/snytax_highliters/hljs-a11y-dark.css b/_sass/snytax_highliters/hljs-a11y-dark.css new file mode 100644 index 0000000..b93b742 --- /dev/null +++ b/_sass/snytax_highliters/hljs-a11y-dark.css @@ -0,0 +1,99 @@ +/* a11y-dark theme */ +/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ +/* @author: ericwbailey */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #d4d0ab; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #ffa07a; +} + +/* Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f5ab35; +} + +/* Yellow */ +.hljs-attribute { + color: #ffd700; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #abe338; +} + +/* Blue */ +.hljs-title, +.hljs-section { + color: #00e0e0; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #dcc6e0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2b2b2b; + color: #f8f8f2; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +@media screen and (-ms-high-contrast: active) { + .hljs-addition, + .hljs-attribute, + .hljs-built_in, + .hljs-builtin-name, + .hljs-bullet, + .hljs-comment, + .hljs-link, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-params, + .hljs-string, + .hljs-symbol, + .hljs-type, + .hljs-quote { + color: highlight; + } + + .hljs-keyword, + .hljs-selector-tag { + font-weight: bold; + } +} diff --git a/_sass/snytax_highliters/hljs-a11y-light.css b/_sass/snytax_highliters/hljs-a11y-light.css new file mode 100644 index 0000000..d9d5f03 --- /dev/null +++ b/_sass/snytax_highliters/hljs-a11y-light.css @@ -0,0 +1,99 @@ +/* a11y-light theme */ +/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ +/* @author: ericwbailey */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #696969; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #d91e18; +} + +/* Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #aa5d00; +} + +/* Yellow */ +.hljs-attribute { + color: #aa5d00; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #008000; +} + +/* Blue */ +.hljs-title, +.hljs-section { + color: #007faa; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #7928a1; +} + +/* .hljs { + display: block; + overflow-x: auto; + background: #fefefe; + color: #545454; + padding: 0.5em; +} */ + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +@media screen and (-ms-high-contrast: active) { + .hljs-addition, + .hljs-attribute, + .hljs-built_in, + .hljs-builtin-name, + .hljs-bullet, + .hljs-comment, + .hljs-link, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-params, + .hljs-string, + .hljs-symbol, + .hljs-type, + .hljs-quote { + color: highlight; + } + + .hljs-keyword, + .hljs-selector-tag { + font-weight: bold; + } +} diff --git a/home.md b/home.md index b47561c..84ab9a7 100644 --- a/home.md +++ b/home.md @@ -8,7 +8,12 @@ seo: name: Just the Class --- -# Just the Class +# UC Berkeley Class Site Template + +

+ +

+ Just the Class is a GitHub Pages template developed for the purpose of quickly deploying course websites. In addition to serving plain web pages and files, it provides a boilerplate for: diff --git a/spec/accessibility_spec.rb b/spec/accessibility_spec.rb index cb7c082..1bb3a21 100644 --- a/spec/accessibility_spec.rb +++ b/spec/accessibility_spec.rb @@ -6,26 +6,6 @@ # spec_helper ensures the webiste is built and can be served locally require 'spec_helper' -def site_url - @site_url ||= YAML.load_file(RSPEC_CONFIG_FILE)['url'] + YAML.load_file(RSPEC_CONFIG_FILE)['baseurl'] -end - -def load_site_urls - puts "Running accessibility tests, expected deploy URL: #{site_url}" - # TODO: Handle case where build is not in _site - sitemap_text = File.read('_site/sitemap.xml') - sitemap_links = sitemap_text.scan(%r{.+}) - sitemap_links.filter_map do |link| - link = link.gsub("#{site_url}", '').gsub('', '') - # Skip non-html pages - # (FUTURE?) Are there other pages that should be audited for accessibility? - # (e.g. PDFs, documents. They'd need a different checker.) - next unless link.end_with?('.html') || link.end_with?('/') - - link - end.sort -end - ALL_PAGES = load_site_urls puts "Running tests on #{ALL_PAGES.count} pages." puts " - #{ALL_PAGES.join("\n - ")}\n#{'=' * 72}\n\n" @@ -33,7 +13,6 @@ def load_site_urls # Axe-core test standards groups # See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#axe-core-tags required_a11y_standards = %i[wcag2a wcag2aa] -# These are currently skipped until the basic tests are passing. complete_a11y_standards = %i[wcag21a wcag21 wcag22aa best-practice secion508] # axe-core rules that are not required to be accessible / do not apply @@ -45,21 +24,19 @@ def load_site_urls ] ALL_PAGES.each do |path| - describe "'#{path}' is accessible", :js, type: :feature do + describe 'page is accessible', :js, type: :feature do before do visit(path) end - # These tests should always be enabled. - it 'according to WCAG 2.0 AA' do + it "#{path} meets WCAG 2.0" do expect(page).to be_axe_clean .according_to(*required_a11y_standards, "#{path} does NOT meet WCAG 2.0 AA") .skipping(*skipped_rules) .excluding(*excluded_elements) end - it 'according to WCAG 2.2 AA', - skip: 'Berkeley: (June 2024) these tests are skipped until the basic tests are passing' do + it "#{path} meets WCAG 2.2" do expect(page).to be_axe_clean .according_to(*complete_a11y_standards) .skipping(*skipped_rules) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index af4c1fa..9d9a4af 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,17 +16,127 @@ # # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +require 'jekyll' require 'rspec' +require 'rack' require 'yaml' +require 'webrick' require 'capybara/rspec' -require 'rack/jekyll' +require 'capybara/dsl' +require 'capybara-screenshot/rspec' +require 'capybara/session' + require 'rack/test' require 'axe-rspec' require 'axe-capybara' -RSPEC_CONFIG_FILE = '_config.yml' or ENV.fetch('RSPEC_CONFIG_FILE', nil) +# ------------ +# Tools to build / compile the Jekyll site and extract the sitemap +def site_config + # TODO(template): We should standardize the build for specs + # Consider simplifying baseurl + # Consider forcing the desination folder + # Override the local URL too? Would it break the sitemap? + # Note: Config keys must be strings and thus use => style hashes. + @site_config ||= Jekyll.configuration({ + 'sass' => { 'quiet_deps' => true } + }) +end + +@site = Jekyll::Site.new(site_config) +@site.process +puts 'Site build complete' + +def load_site_urls + puts 'Running accessibility tests' + sitemap_text = File.read('_site/sitemap.xml') + sitemap_links = sitemap_text.scan(%r{.+}) + sitemap_links.filter_map do |link| + link = link.gsub("#{site_config['url']}", '').gsub('', '') + # Skip non-html pages + # (FUTURE?) Are there other pages that should be audited for accessibility? + # (e.g. PDFs, documents. They'd need a different checker.) + next unless link.end_with?('.html') || link.end_with?('/') + + link + end.sort +end +# -------- + +# This is the root of the repository, e.g. the bjc-r directory +# Update this is you move this file. +REPO_ROOT = File.expand_path('../', __dir__) + +# https://nts.strzibny.name/how-to-test-static-sites-with-rspec-capybara-and-webkit/ +class StaticSite + attr_reader :root, :server + + # TODO: Rack::File will be deprecated soon. Find a better solution. + def initialize(root) + @root = root + @server = Rack::Files.new(root) + end + + def call(env) + # Remove the /baseurl prefix, which is present in all URLs, but not in the file system. + path = "_site#{env['PATH_INFO'].gsub(site_config['baseurl'], '/')}" + env['PATH_INFO'] = if path.end_with?('/') && exists?("#{path}index.html") + "#{path}index.html" + elsif !exists?(path) && exists?("#{path}.html") + "#{path}.html" + else + path + end + + server.call(env) + end + + def exists?(path) + File.exist?(File.join(root, path)) + end +end +# --------- + +Capybara.register_driver :chrome_headless do |app| + options = Selenium::WebDriver::Chrome::Options.new + options.add_argument('--headless') + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + # macbook air ~13" screen size, with an absurd height for full size screenshots. + options.add_argument('--window-size=1280,4000') + + Capybara::Selenium::Driver.new(app, browser: :chrome, options:) +end + +# Change default_driver to :selenium_chrome if you want to actually see the tests running in a browser locally. +# Should be :chrome_headless in CI though. +Capybara.default_driver = :chrome_headless +Capybara.javascript_driver = :chrome_headless + +Capybara::Screenshot.register_driver(:chrome_headless) do |driver, path| + driver.save_screenshot(path) +end + +Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example| + "tmp/capybara/screenshot_#{example.description.gsub('/', '-').gsub(' ', '-').gsub(%r{^.*/spec/}, '')}" +end + +Capybara::Screenshot.autosave_on_failure = true +Capybara::Screenshot.append_timestamp = false +Capybara::Screenshot.prune_strategy = :keep_last_run + +# Setup for Capybara to serve static files served by Rack +Capybara.server = :webrick +Capybara.app = Rack::Builder.new do + use Rack::Lint + run StaticSite.new(REPO_ROOT) + # map '/' do + # end +end.to_app + +# --------- RSpec.configure do |config| # Allow rspec to use `--only-failures` and `--next-failure` flags # Ensure that `tmp` is in your `.gitignore` file @@ -59,35 +169,5 @@ # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups - Capybara.register_driver :chrome_headless do |app| - options = Selenium::WebDriver::Chrome::Options.new - options.add_argument('--headless') - options.add_argument('--no-sandbox') - options.add_argument('--disable-dev-shm-usage') - # macbook air ~13" screen size - options.add_argument('--window-size=1280,800') - - Capybara::Selenium::Driver.new(app, browser: :chrome, options:) - end - - # Change default_driver to :selenium_chrome if you want to actually see the tests running in a browser locally. - # Should be :chrome_headless in CI though. - Capybara.default_driver = :chrome_headless - Capybara.javascript_driver = :chrome_headless - - # Configure Capybara to load the website through rack-jekyll. - # (force_build: true) builds the site before the tests are run, - # so our tests are always running against the latest version - # of our jekyll site. - jekyll_app = Rack::Jekyll.new(force_build: true, config: RSPEC_CONFIG_FILE) - - # https://stackoverflow.com/questions/52506822/testing-a-jekyll-site-with-rspec-and-capybara-getting-a-bizarre-race-case-on-rs - sleep 0.1 while jekyll_app.compiling? - - Capybara.app = jekyll_app - - # Configure Capybara server (otherwise it will error and say to use webrick or puma) - Capybara.server = :webrick - config.include Capybara::DSL end diff --git a/spec/support/spec_summary.rb b/spec/support/spec_summary.rb new file mode 100644 index 0000000..f83085e --- /dev/null +++ b/spec/support/spec_summary.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# Summarize the axe rspec failures into aggregate counts +# TODO: This should be an RSpec formatter + +require 'json' + +RESULTS_PATH = File.join(File.dirname(__FILE__), '..', '..', 'tmp/rspec_output.json') +AXE_CASE_TITLE = /\n\s*\n\s*\d+\)\s+([-\w]+):/ + +def failing_specs(results_data) + results_data['examples'].filter do |ex| + ex['status'] == 'failed' + end +end + +def summarize_results(results) + failing_specs(results).map do |ex| + ex['exception']['message'].scan(AXE_CASE_TITLE) + end.flatten.tally +end + +# rubocop:disable Metrics/AbcSize +# rubocop:disable Metrics/MethodLength +def group_results(results) + all_cases_list = failing_specs(results).map do |ex| + msg = ex['exception']['message'] + msg.gsub!(/\nInvocation:.*;/, '') + cases = msg.split(AXE_CASE_TITLE) + cases.delete_at(0) + Hash[*cases].transform_values { |v| { page: ex['full_description'], message: v } } + end + results = {} + results.default = [] + all_cases_list.each do |test_hash| + test_hash.each do |axe_name, failure| + if results.key?(axe_name) + results[axe_name] << failure + else + results[axe_name] = [failure] + end + end + end + results +end +# rubocop:enable Metrics/AbcSize +# rubocop:enable Metrics/MethodLength + +def test_failures_with_pages(summary_group) + summary_group.transform_values { |list| list.map { |h| h[:page] } } +end + +def nicely_print(hash) + hash.each do |key, values| + puts "#{key}:" + values.each { |item| puts("\t#{item}") } + end +end + +def print_summary + results_data = JSON.parse(File.read(RESULTS_PATH)) + failing_tests_by_type = summarize_results(results_data) + pp(failing_tests_by_type) + puts "Total: #{failing_tests_by_type.values.sum} failures." + + puts "\n#{'-' * 16}" + summary_group = group_results(results_data) + nicely_print(test_failures_with_pages(summary_group)) +end + +print_summary