wip: wcag structure
Some checks failed
/ Run tests (push) Successful in 7m57s
/ Run system tests (push) Failing after 4m18s
/ Build, push and deploy image (push) Failing after 59s

This commit is contained in:
david 2025-05-16 19:02:33 +02:00
parent 4c31dbbed0
commit 4dd445be57
48 changed files with 461 additions and 137 deletions

View file

@ -1,3 +1,3 @@
web: RUBY_DEBUG_OPEN=true bin/rails server -b 0 web: RUBY_DEBUG_OPEN=true bin/rails server -b 0
js: yarn build --watch js: yarn build --watch=forever
css: yarn watch:css css: yarn watch:css

View file

@ -2,3 +2,7 @@
`rails new -n a11yist -d sqlite3 --skip-action-mailbox --css bootstrap --js esbuild .` `rails new -n a11yist -d sqlite3 --skip-action-mailbox --css bootstrap --js esbuild .`
## Installation
To [install](readme)

View file

@ -25,10 +25,6 @@ class ApplicationController < ActionController::Base
label: Project.model_name.human(count: 2), label: Project.model_name.human(count: 2),
icon: :'folder', icon: :'folder',
path: :projects }, path: :projects },
# {
# label: Report.model_name.human(count: 2),
# icon: :'journal-text',
# path: :reports },
{ {
label: I18n.t("backoffice"), label: I18n.t("backoffice"),
icon: :gear, icon: :gear,
@ -36,7 +32,8 @@ class ApplicationController < ActionController::Base
active: %w[backoffice checklists checks links link_categories].include?(controller_name) }, active: %w[backoffice checklists checks links link_categories].include?(controller_name) },
{ {
label: "Konto", label: "Konto",
path: profile_path path: profile_path,
icon: "person-circle"
} }
] ]
else else

View file

@ -81,7 +81,7 @@ class ChecksController < ApplicationController
# Only allow a list of trusted parameters through. # Only allow a list of trusted parameters through.
def check_params def check_params
params.require(:check).permit(:principle_id, params.require(:check).permit(:guideline_id,
:number, :number,
:name_de, :name_de,
:name_en, :name_en,

View file

@ -5,6 +5,7 @@ module BackofficeMenu
[ [
{ label: "Einstellungen", icon: :sliders, path: :backoffice }, { label: "Einstellungen", icon: :sliders, path: :backoffice },
{ label: Checklist.model_name.human(count: 2), icon: :'list-check', path: :checklists }, { label: Checklist.model_name.human(count: 2), icon: :'list-check', path: :checklists },
{ label: Guideline.model_name.human(count: 2), icon: :'rulers', path: :guidelines },
{ label: Check.model_name.human(count: 2), icon: :check2, path: :checks }, { label: Check.model_name.human(count: 2), icon: :check2, path: :checks },
{ label: Link.model_name.human(count: 2), icon: :link, path: :links }, { label: Link.model_name.human(count: 2), icon: :link, path: :links },
{ label: LinkCategory.model_name.human(count: 2), icon: :folder, path: :link_categories } ] { label: LinkCategory.model_name.human(count: 2), icon: :folder, path: :link_categories } ]

View file

@ -0,0 +1,58 @@
class GuidelinesController < BackofficeController
before_action :set_guideline, only: %i[ show edit update destroy ]
# GET /guidelines
def index
@guidelines = Guideline.all
end
# GET /guidelines/1
def show
end
# GET /guidelines/new
def new
@guideline = Guideline.new
end
# GET /guidelines/1/edit
def edit
end
# POST /guidelines
def create
@guideline = Guideline.new(guideline_params)
if @guideline.save
redirect_to @guideline, notice: "Guideline was successfully created."
else
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /guidelines/1
def update
if @guideline.update(guideline_params)
redirect_to @guideline, notice: "Guideline was successfully updated.", status: :see_other
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /guidelines/1
def destroy
@guideline.destroy!
redirect_to guidelines_url, notice: "Guideline was successfully destroyed.", status: :see_other
end
private
# Use callbacks to share common setup or constraints between actions.
def set_guideline
@guideline = Guideline.find(params[:id])
end
# Only allow a list of trusted parameters through.
def guideline_params
params.require(:guideline).permit(:principle_id, :number, :name_de, :name_en, :description_de, :description_en)
end
end

View file

@ -4,7 +4,11 @@ import * as bootstrap from "bootstrap"
// Connects to data-controller="toast" // Connects to data-controller="toast"
export default class extends Controller { export default class extends Controller {
connect() { connect() {
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(this.element) const shownKey = `toastsShown[${this.element.getAttribute("data-ts")}]`
toastBootstrap.show() if(!window.sessionStorage.getItem(shownKey)) {
window.sessionStorage.setItem(shownKey, Date.now());
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(this.element)
toastBootstrap.show()
}
} }
} }

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Check < ApplicationRecord class Check < ApplicationRecord
belongs_to :principle belongs_to :guideline
has_and_belongs_to_many :links has_and_belongs_to_many :links
has_and_belongs_to_many :standards has_and_belongs_to_many :standards
@ -51,9 +51,9 @@ class Check < ApplicationRecord
:standard_text, :standard_text,
:powerpoint_text :powerpoint_text
validates :number, uniqueness: true, presence: true validates :number, uniqueness: { scope: :guideline_id }, presence: true
before_validation { self.number = self.class.maximum(:id).to_i + 1 unless self.number.present? } # before_validation { self.number = self.class.maximum(:id).to_i + 1 unless self.number.present? }
scope(:search, lambda { |terms| scope(:search, lambda { |terms|
# TODO: Search only fields for current locale. # TODO: Search only fields for current locale.
@ -129,4 +129,20 @@ class Check < ApplicationRecord
def external_number def external_number
[ external_number_1, external_number_2, external_number_3 ].compact_blank.join(".") [ external_number_1, external_number_2, external_number_3 ].compact_blank.join(".")
end end
def external_number
[
guideline.principle.id,
guideline.number,
number
].compact.join(".")
end
def full_number
external_number
end
def to_s
display_label
end
end end

18
app/models/guideline.rb Normal file
View file

@ -0,0 +1,18 @@
class Guideline < ApplicationRecord
belongs_to :principle
has_many :checks
has_rich_text :description_de
has_rich_text :description_en
translates_attributes :name, :description
def to_s
"#{full_number} #{t_name}"
end
def full_number
[ principle.id, number ].join(".")
end
end

View file

@ -21,4 +21,8 @@ class Report < ApplicationRecord
success_criteria: export_success_criteria success_criteria: export_success_criteria
} }
end end
def test
139
end
end end

View file

@ -7,6 +7,12 @@
<%= link_to Checklist.model_name.human(count: Checklist.count), :checklists %> <%= link_to Checklist.model_name.human(count: Checklist.count), :checklists %>
</p> </p>
<p>
<i class="bi bi-rulers"></i>
<%= Guideline.count %>
<%= link_to Guideline.model_name.human(count: Guideline.count), :guidelines %>
</p>
<p> <p>
<i class="bi bi-check2"></i> <i class="bi bi-check2"></i>
<%= Check.count %> <%= Check.count %>
@ -27,4 +33,4 @@
<li> <li>
<%= link_to "ZIP Backup herunterladen", admin_backup_url(format: :zip), class: " ", data: { turbo_prefetch: false, frame: "_top", turbo: false } %> <%= link_to "ZIP Backup herunterladen", admin_backup_url(format: :zip), class: " ", data: { turbo_prefetch: false, frame: "_top", turbo: false } %>
</li> </li>
</ul> </ul>

View file

@ -11,8 +11,8 @@ div id=dom_id(check)
th = Check.human_attribute_name(:number) th = Check.human_attribute_name(:number)
td = check.number td = check.number
tr tr
th = Principle.model_name.human th = Guideline.model_name.human
td = check.principle&.t_name td = check.guideline&.name_en
tr tr
th = Standard.model_name.human(count: check.standard_ids.size) th = Standard.model_name.human(count: check.standard_ids.size)
td = check.standards.map(&:t_name).sort_by(&:downcase).join(", ") td = check.standards.map(&:t_name).sort_by(&:downcase).join(", ")
@ -80,4 +80,4 @@ div id=dom_id(check)
ul ul
- check.links.select{ _1.link_category == category }.map { |link| link_to link.text, link.url, target: :_blank }.each do |link| - check.links.select{ _1.link_category == category }.map { |link| link_to link.text, link.url, target: :_blank }.each do |link|
li = link li = link

View file

@ -1,9 +1,9 @@
= bootstrap_form_with(model: check, remote: true, data: { controller: "unsaved-changes" }) do |form| = bootstrap_form_with(model: check, remote: true, data: { controller: "unsaved-changes" }) do |form|
h2 Details h2 Details
= multilang_form_field(form, :name) = multilang_form_field(form, :name)
= form.text_field :number, required: false
.row .row
= form.collection_radio_buttons(:principle_id, Principle.all.sort_by(&:t_name), :id, :t_name) { |b| b.label(class: "col-md-2") { b.radio_button + b.text } } = form.collection_radio_buttons(:guideline_id, Guideline.all.sort_by(&:full_number), :id, :to_s) { |b| b.label(class: "col-md-2") { b.radio_button + b.text } }
= form.text_field :number, required: false
= form.collection_check_boxes :standard_ids, Standard.all.sort_by{ _1.t_name.downcase }, :id, :t_name, include_blank: true = form.collection_check_boxes :standard_ids, Standard.all.sort_by{ _1.t_name.downcase }, :id, :t_name, include_blank: true
h2 Einschränkung/Zugänglichkeit h2 Einschränkung/Zugänglichkeit

View file

@ -6,4 +6,5 @@
- if element.persisted? - if element.persisted?
= safe_display(element.screenshot) { tag.div(link_to(_1.filename.to_s, _1), class: "mb-3") } = safe_display(element.screenshot) { tag.div(link_to(_1.filename.to_s, _1), class: "mb-3") }
= form.submit class: "btn btn-primary" = form.submit class: "btn btn-primary"
= link_to("Abbrechen", element.persisted? ? element : element.report, class: "btn btn-outline-secondary") - unless modal?
= link_to("Abbrechen", element.persisted? ? element : element.report, class: "btn btn-outline-secondary")

View file

@ -1,26 +1,3 @@
/nav
= link_to(@report.name, "##{dom_id(@report)}")
ul
li = link_to("Inhaltsverzeichnis", "#toc")
li
= link_to('Testbericht')
ul
- @report.pages.select { |p| p.elements.any? { |e| e.success_criteria.any?(&:failed?) } }.each do |page|
li
= link_to("#{page.position} #{page.path}", "##{dom_id(page)}")
ul
- page.elements.select { |e| e.success_criteria.any?(&:failed?) }.each do |element|
li
= link_to("#{element.number} #{element.title}")
ul
- element.success_criteria.select(&:failed?).each do |sc|
li = link_to("#{sc.number} #{sc.title}", "##{dom_id(sc)}")
li
= link_to("Anhang")
ul
- @failed_success_criteria.group_by(&:check).each do |check, scs|
li = link_to(check.display_label)
h1.title id=dom_id(@report) = @report.name h1.title id=dom_id(@report) = @report.name
h2 1 Einschätzung h2 1 Einschätzung
@ -42,18 +19,18 @@ h2 2 Protokoll
- current_page_pos += 1 - current_page_pos += 1
- current_element_pos = 0 - current_element_pos = 0
h3 = "2.#{current_page_pos} #{page.path}" h3 = "2.#{current_page_pos} #{page.path}"
p
strong URL
= page.url
- page.elements.select { |e| e.success_criteria.any? { _1.failed? } }.each do |element| - page.elements.select { |e| e.success_criteria.any? { _1.failed? } }.each do |element|
- current_element_pos += 1 - current_element_pos += 1
- current_abs_element_pos += 1 - current_abs_element_pos += 1
- current_sc_pos = 0 - current_sc_pos = 0
h4 = "2.#{current_page_pos}.#{current_element_pos} #{element.title}" h4 = "2.#{current_page_pos}.#{current_element_pos} #{element.title}"
/h3 = "2.#{current_abs_element_pos} #{element.title}" /h3 = "2.#{current_abs_element_pos} #{element.title}"
p
strong Pfad:
span =< page.path
= safe_display(element.screenshot) { image_tag(_1.representation(resize_to_fit: [250, 250]))} = safe_display(element.screenshot) { image_tag(_1.representation(resize_to_fit: [250, 250]))}
= element.description = element.description
- element.success_criteria.select{ _1.failed? }.each do |sc| - element.success_criteria.select(&:failed?).each do |sc|
- current_sc_pos += 1 - current_sc_pos += 1
/h4 /h4
= "2.#{current_abs_element_pos}.#{current_sc_pos} #{sc.title}" = "2.#{current_abs_element_pos}.#{current_sc_pos} #{sc.title}"

View file

@ -0,0 +1,7 @@
<%= bootstrap_form_with(model: guideline) do |form| %>
<%= form.collection_select :principle_id, Principle.all.sort_by(&:id), :id, :t_name %>
<%= form.number_field :number %>
<%= form.text_field :name_de %>
<%= form.rich_text_area :description_de %>
<%= form.submit %>
<% end %>

View file

@ -0,0 +1,15 @@
<div id="<%= dom_id guideline %>">
<p>
<strong>Principle:</strong>
<%= guideline.principle.t_name %>
</p>
<%= guideline.description_de %>
<h2 class="mt-4">Richtlinien</h2>
<% guideline.checks.each do |check| %>
<section class="pt-3">
<h3 class="fs-5"><%= link_to check %></h3>
<%= check.t_criterion %>
</section>
<% end %>
</div>

View file

@ -0,0 +1,2 @@
json.extract! guideline, :id, :principle_id, :number, :name_de, :created_at, :updated_at
json.url guideline_url(guideline, format: :json)

View file

@ -0,0 +1,8 @@
<h1><%= t("scaffold.pagetitle_edit", model: Guideline.model_name.human) %></h1>
<%= render "form", guideline: @guideline %>
<div class="action-row">
<%= link_to t("scaffold.link_show", model: Guideline.model_name.human), @guideline %>
<%= link_to t("scaffold.link_index", model: Guideline.model_name.human(count: 2)), guidelines_path %>
</div>

View file

@ -0,0 +1,28 @@
<h1><%= t("scaffold.pagetitle_index", model: Guideline.model_name.human(count: 2)) %></h1>
<table class="table table-striped">
<thead>
<tr>
<th><%= Guideline.human_attribute_name(:name_de) %></th>
<th><%= Guideline.human_attribute_name(:description_de) %></th>
</thead>
<tbody>
<% @guidelines.each do |guideline| %>
<tr>
<td><%= link_to(guideline, url_for(guideline)) %></td>
<td><%= link_to(guideline.description_de, url_for(guideline)) %></td>
</tr>
<% end %>
</tbody>
</table>
<div class="action-row">
<%= link_to t("scaffold.link_new", model: Guideline.model_name.human), new_guideline_path %>
</div>

View file

@ -0,0 +1 @@
json.array! @guidelines, partial: "guidelines/guideline", as: :guideline

View file

@ -0,0 +1,7 @@
<h1><%= t("scaffold.pagetitle_new", model: Guideline.model_name.human) %></h1>
<%= render "form", guideline: @guideline %>
<div class="action-row">
<%= link_to t("scaffold.link_index", model: Guideline.model_name.human(count: 2)), guidelines_path %>
</div>

View file

@ -0,0 +1,9 @@
<h1><%= @guideline %></h1>
<%= render @guideline %>
<div class="action-row">
<%= link_to t("scaffold.link_edit", model: @guideline.model_name.human), edit_guideline_path(@guideline) %>
<%= link_to t("scaffold.link_index", model: @guideline.model_name.human(count: 2)), guidelines_path %>
<%= button_to t("scaffold.link_destroy", model: @guideline.model_name.human), @guideline, method: :delete, class: "btn btn-outline-danger" %>
</div>

View file

@ -0,0 +1 @@
json.partial! "guidelines/guideline", guideline: @guideline

View file

@ -19,6 +19,12 @@ h1 Dashboard
li = link_to(r.name, r) li = link_to(r.name, r)
.col-md-6 .col-md-6
h2 Browser Erweiterungen
p = link_to "Language Tools KI Korrektur", "https://languagetool.org/services#browsers"
p = link_to "DeepL Firefox Extension", "https://www.deepl.com/en/firefox-extension"
p = link_to "DeepL Chrome Extension", "https://www.deepl.com/en/chrome-extension"
p = link_to "DeepL Edge Extension", "https://www.deepl.com/en/edge-extension"
h2 Hotkeys h2 Hotkeys
p Auf der Bericht-Ausfüllen Seite können folgende Shortcuts verwendet werden: p Auf der Bericht-Ausfüllen Seite können folgende Shortcuts verwendet werden:
dl dl

View file

@ -1,4 +1,4 @@
.toast class="#{alert ? "text-bg-danger" : "text-bg-info"}" role="alert" aria-live="assertive" aria-atomic="true" data={ controller: "toast" } .toast class="#{alert ? "text-bg-danger" : "text-bg-info"}" role="alert" aria-live="assertive" aria-atomic="true" data={ controller: "toast", ts: Time.now.to_f }
.toast-header .toast-header
/img src="..." class="rounded me-2" alt="..."> /img src="..." class="rounded me-2" alt="...">
/strong.me-auto = heading /strong.me-auto = heading

View file

@ -1,5 +1,5 @@
doctype html doctype html
html data-bs-theme="#{cookies[:"modeTheme"] || "light"}" data-controller="set-theme" html lang=:de data-bs-theme="#{cookies[:"modeTheme"] || "light"}" data-controller="set-theme"
head head
title a11ydive title a11ydive
meta[name="viewport" content="width=device-width,initial-scale=1"] meta[name="viewport" content="width=device-width,initial-scale=1"]

View file

@ -2,4 +2,5 @@
= form.text_field :path = form.text_field :path
= form.text_field :url = form.text_field :url
= form.submit = form.submit
= link_to("Abbrechen", report_path(@page.report), class: "btn btn-outline-secondary") - unless modal?
= link_to("Abbrechen", report_path(@page.report), class: "btn btn-outline-secondary")

View file

@ -7,4 +7,5 @@
= form.rich_text_area :test_comment = form.rich_text_area :test_comment
= form.submit class: "btn btn-primary" = form.submit class: "btn btn-primary"
- unless modal? - unless modal?
p Not MODAL
=< link_to "Abbrechen", success_criterion.persisted? ? success_criterion : success_criterion.element, class: "btn btn-outline-secondary" =< link_to "Abbrechen", success_criterion.persisted? ? success_criterion : success_criterion.element, class: "btn btn-outline-secondary"

View file

@ -8,4 +8,4 @@ fi
# Default to port 3000 if not specified # Default to port 3000 if not specified
export PORT="${PORT:-3000}" export PORT="${PORT:-3000}"
exec foreman start -f /app/Procfile.dev "$@" exec foreman start -f Procfile.dev "$@"

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
if [ -f /app/tmp/pids/server.pid ]; then if [ -f tmp/pids/server.pid ]; then
rm /app/tmp/pids/server.pid rm tmp/pids/server.pid
fi fi
exec "$@" exec "$@"

View file

@ -1 +1 @@
vRtWWWCynlFuZljZsCQOV5D6xZlwp/LkuGZ2bPp1IuZiCRfJ2F8CXbubRavkqM0xxpHWkC7hiQ0lSv/dCPtaujG+aJ2Q0+KSw81kJ3xiYd/AiR3ZxO9prk5Zsf0LPLoONGBbYurJIb37hAWQ7h1r1qORLbtQGLb9pYlynObhXvkaYQR5E7Jr0Rcnon0d2uKJ13ukDAHUiIgwCE5/f+Y6UVllTLBCk0a+YDZvkv7xsYNpTTD9bgjpAU/pg6jv1xY0k/gZ8SFRsSQaJs8CYtbhMsyZBvImtguGfc7U7B4cFrgdzUPY473vBKBqm6O0StNagS5muP3/YLLN7xcrRDTBi9n3qllATlfJ9cxZ+JCWZJmyqINI4X+T/sc3lpzlo/grW0W+HwQ/4CZ3LkCN/OOqZlG1HL1u--NhnYNqgGpqSnh7Ut--KGndHl8kyj/uAjRG12R3zg== TjpgJe/DwNAzOraDbzXUo20hxCLkOn72DlXYkIouG0crCD3m4/LAiwDOlOWwsCO5Je6FzqKZniLUqSfVthChxewvZn+PY7XGTLECbB9gjSpRC6hERUxKIirLot+CH7lkMlM3f4o3NPf2I4vs8j6hooXcc8Vd8l1uMHOU1RHd+8EfPmTetqd0IETnEUdXigL50yjcbpxy8jHGdaeA8hHU3F+jwk7gRv11uVgEfn069qD9tc5AlAFWdnYJj/ZadAX2+bimHyne5Y12gmRAiqu95KXzUs0OlI+Vx9lpFyMQNacuKPPx0AeeBafjnMBozXOSU7MMCBUGzig07Kg+/tJSiekeLX0X2VEj3Ecqe9nL54fAGVSqJmwT19KvjeS4WfjvFDff9KAY4H+vKzfkBQzMFOTJjnMTNHa+dtNly0kUphwDtsFWDhF3PaZQIRCeI7RIXKhFMgrRFE6UL4AFFzhocQ1DAwaZElXFJaKjiCWWCY+acLCRz2AnriR08KMsEGN54nMtcGcWnUS2ewr/txLQDkipjADYmjoxsz+rDqp2IH+4y4ZFJf8=--jX6TlsuL0nwliAuN--lnbA1H3K7bwrmIjgJzAT9A==

View file

@ -0,0 +1,6 @@
if Rails.application.credentials[:deepl_api_key]
DeepL.configure do |config|
config.auth_key = Rails.application.credentials[:deepl_api_key]
config.host = "https://api-free.deepl.com"
end
end

View file

@ -1,5 +1,6 @@
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
Rails.application.routes.draw do Rails.application.routes.draw do
resources :guidelines
resource :session resource :session
resources :passwords, param: :token resources :passwords, param: :token
namespace :admin do namespace :admin do

View file

@ -0,0 +1,12 @@
class CreateGuidelines < ActiveRecord::Migration[8.0]
def change
create_table :guidelines do |t|
t.references :principle, null: false, foreign_key: true
t.integer :number
t.string :name_de
t.string :name_en
t.timestamps
end
end
end

View file

@ -0,0 +1,6 @@
class AddGuidelineIdToChecks < ActiveRecord::Migration[8.0]
def change
add_reference :checks, :guideline, null: false, foreign_key: true
remove_reference :checks, :principle
end
end

View file

@ -0,0 +1,5 @@
class RemoveUniqueNumberConstraintOnChecks < ActiveRecord::Migration[8.0]
def change
remove_index :checks, :number
end
end

20
db/schema.rb generated
View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do ActiveRecord::Schema[8.0].define(version: 2025_01_01_171836) do
create_table "account_remember_keys", force: :cascade do |t| create_table "account_remember_keys", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.datetime "deadline", null: false t.datetime "deadline", null: false
@ -94,7 +94,6 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do
t.boolean "cognitive", default: false, null: false t.boolean "cognitive", default: false, null: false
t.boolean "applicable_to_web", default: false, null: false t.boolean "applicable_to_web", default: false, null: false
t.boolean "applicable_to_app", default: false, null: false t.boolean "applicable_to_app", default: false, null: false
t.integer "principle_id"
t.integer "conformity_level" t.integer "conformity_level"
t.integer "priority" t.integer "priority"
t.boolean "manual_test", default: true, null: false t.boolean "manual_test", default: true, null: false
@ -106,8 +105,8 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do
t.integer "external_number_1" t.integer "external_number_1"
t.integer "external_number_2" t.integer "external_number_2"
t.integer "external_number_3" t.integer "external_number_3"
t.index ["number"], name: "index_checks_on_number", unique: true t.integer "guideline_id", null: false
t.index ["principle_id"], name: "index_checks_on_principle_id" t.index ["guideline_id"], name: "index_checks_on_guideline_id"
end end
create_table "checks_links", id: false, force: :cascade do |t| create_table "checks_links", id: false, force: :cascade do |t|
@ -131,6 +130,16 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do
t.index ["page_id"], name: "index_elements_on_page_id" t.index ["page_id"], name: "index_elements_on_page_id"
end end
create_table "guidelines", force: :cascade do |t|
t.integer "principle_id", null: false
t.integer "number"
t.string "name_de"
t.string "name_en"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["principle_id"], name: "index_guidelines_on_principle_id"
end
create_table "link_categories", force: :cascade do |t| create_table "link_categories", force: :cascade do |t|
t.string "name" t.string "name"
t.text "description" t.text "description"
@ -230,8 +239,9 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "checklist_entries", "checklists" add_foreign_key "checklist_entries", "checklists"
add_foreign_key "checklist_entries", "checks" add_foreign_key "checklist_entries", "checks"
add_foreign_key "checks", "principles" add_foreign_key "checks", "guidelines"
add_foreign_key "elements", "pages" add_foreign_key "elements", "pages"
add_foreign_key "guidelines", "principles"
add_foreign_key "links", "link_categories" add_foreign_key "links", "link_categories"
add_foreign_key "pages", "reports" add_foreign_key "pages", "reports"
add_foreign_key "reports", "projects" add_foreign_key "reports", "projects"

View file

@ -72,6 +72,8 @@ Principle.create!(name_de: "Verständlich", name_en: "Understandable")
Principle.create!(name_de: "Robust", name_en: "Robust") Principle.create!(name_de: "Robust", name_en: "Robust")
Principle.create!(name_de: "Sonstige", name_en: "Other") Principle.create!(name_de: "Sonstige", name_en: "Other")
LinkCategory.create!(name: "Verstehen")
LinkCategory.create!(name: "WCAG Quick Reference")
LinkCategory.create!(name: "Tools") LinkCategory.create!(name: "Tools")
LinkCategory.create!(name: "Beispiele") LinkCategory.create!(name: "Beispiele")
LinkCategory.create!(name: "Artikel") LinkCategory.create!(name: "Artikel")
@ -92,4 +94,3 @@ Link.create!(url: "https://www.a11yproject.com/",
link_category: LinkCategory.find_by(name: "Artikel")) link_category: LinkCategory.find_by(name: "Artikel"))
User.find_or_initialize_by(email_address: "admin@example.com").update!(password: "password") User.find_or_initialize_by(email_address: "admin@example.com").update!(password: "password")
User.find_or_initialize_by(email_address: "goran@quiet.ch").update!(password: "password")

View file

@ -1,62 +0,0 @@
# This is available in $COMPOSE_PROJECT_NAME
name: a11yist
networks:
traefik:
external: true
services:
app: &app
build:
context: .
restart: "unless-stopped"
command: ["/bin/sh", "-c", "/app/bin/dev"]
volumes:
- ./:/app:cached
- ${PWD}:${PWD}
- ${SSH_AUTH_SOCK}:/ssh-agent
- .devenv/helix:/home/app/.config/helix
- .devenv/fish:/home/app/.config/fish
env_file:
- .env
environment:
RAILS_ENV: development
LOG_LEVEL: debug
TRUSTED_IP: 172.16.0.0/12,192.168.0.0/16,10.0.0.0/24
SSH_AUTH_SOCK: /ssh-agent
RAILS_SERVE_STATIC_FILES: 1
APP_HOST: ${COMPOSE_PROJECT_NAME}.localhost
HISTFILE: /app/tmp/.bash_history
PSQL_HISTORY: /app/tmp/.psql_history
IRBRC: /app/.irbrc
SELENIUM_REMOTE_URL: http://chrome:4444/wd/hub
labels:
- traefik.http.routers.app-${COMPOSE_PROJECT_NAME}.entrypoints=http
- traefik.http.routers.app-${COMPOSE_PROJECT_NAME}.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)
- traefik.http.services.app-${COMPOSE_PROJECT_NAME}.loadbalancer.server.port=3000
- traefik.docker.network=traefik
networks:
- traefik
- default
edit:
<<: *app
restart: "no"
labels: []
depends_on: []
command: ["hx", "."]
entrypoint: null
networks:
- default
chrome:
image: selenium/standalone-chrome
shm_size: 2g
labels:
- traefik.http.routers.chrome-${COMPOSE_PROJECT_NAME}.entrypoints=http
- traefik.http.routers.chrome-${COMPOSE_PROJECT_NAME}.rule=Host(`chrome.${COMPOSE_PROJECT_NAME}.localhost`)
- traefik.http.services.chrome-${COMPOSE_PROJECT_NAME}.loadbalancer.server.port=4444
- traefik.docker.network=traefik
networks:
- traefik
- default

View file

@ -1,20 +1,66 @@
# frozen_string_literal: true # frozen_string_literal: true
URL = "https://outline-rocks.github.io/wcag/translations/WCAG21-de/" URL = "https://outline-rocks.github.io/wcag/translations/WCAG21-de/"
WCAG_22_EN_URL ="https://www.w3.org/TR/WCAG22/" WCAG_22_EN_URL ="https://www.w3.org/TR/WCAG22/"
WCAG_22_EN_QREF_URL = "https://www.w3.org/WAI/WCAG22/quickref/"
def translate(input)
DeepL.translate(input, "EN", "DE")
end
def import_wcag22en def import_wcag22en
doc = Nokogiri::HTML5(URI.open(WCAG_22_EN_URL)) doc = Nokogiri::HTML5(URI.open(WCAG_22_EN_URL))
standards = [Standard.find_by(name_de: "WCAG 2.2"), Standard.find_by(name_de: "EN 301 549")] qrefdoc = Nokogiri::HTML5(URI.open(WCAG_22_EN_QREF_URL))
quick_criteria = qrefdoc.css(".guidelines section:has(h4)").each_with_object({}) do |node, h|
h[node.css("h4 strong").first.content] = {
quick_criterion_en: node.css(".sc-content .sc-text p").first.content,
link_url: node.css(".understanding a").first.attribute("href").value,
perm_url: "#{WCAG_22_EN_QREF_URL}##{node.css("article").first.attribute("id").value}"
}
rescue
debugger
end
link_category = LinkCategory.find_by(name: "Verstehen")
qr_category = LinkCategory.find_by(name: "WCAG Quick Reference")
# debugger
# raise ActiveRecord::Rollback
standards = [ Standard.find_by(name_de: "WCAG 2.2"), Standard.find_by(name_de: "EN 301 549") ]
doc.css("section.principle").each do |principle_node| doc.css("section.principle").each do |principle_node|
_principle_id = principle_node.attributes["id"].value _principle_id = principle_node.attributes["id"].value
principle_title = principle_node.css("h2").first.content.scan(/([a-zA-Z]+)/) principle_title = principle_node.css("h2").first.content.scan(/([a-zA-Z]+)/)
principle = Principle.find_by!(name_en: principle_title) principle = Principle.find_or_create_by!(name_en: principle_title)
principle_node.css("section.guideline").each do |guideline_node| principle_node.css("section.guideline").each do |guideline_node|
next unless guideline_node.css("h3").first
puts guideline_node.css("h3").first&.content
puts guideline_node.css_path
g_title = guideline_node.css("h3")
.first
.content
.scan(/Guideline \d+\.\d+ (.*)/)
.first
.first
g_number = guideline_node.css("h3 bdi")
.first
.content
.scan(/\d+\.(\d+)/)
.first
.first
g_text = guideline_node.css("> p").first.content
guideline = Guideline.find_or_create_by(principle: principle, number: g_number, name_en: g_title)
guideline.update(description_en: g_text, description_de: translate(g_text), name_de: translate(g_title)) if guideline.description_de.blank?
guideline_node.css("section.guideline").each do |sc| guideline_node.css("section.guideline").each do |sc|
puts sc.css_path
puts sc.css("h4 bdi")
sc_number, sc_title = sc.css("h4") sc_number, sc_title = sc.css("h4")
.first .first
.content .content
.scan(/Success Criterion (\d+\.\d+\.\d+) (.*)/) .scan(/Success Criterion \d+\.\d+\.(\d+) (.*)/)
.first .first
sc_level = sc.css("p.conformance-level").first&.content&.scan(/\(Level (A+)\)/)&.first&.last sc_level = sc.css("p.conformance-level").first&.content&.scan(/\(Level (A+)\)/)&.first&.last
sc_url = sc.css("a.self-link").first.attr("href") sc_url = sc.css("a.self-link").first.attr("href")
@ -23,20 +69,35 @@ def import_wcag22en
.select { _1.class == Nokogiri::XML::Element } .select { _1.class == Nokogiri::XML::Element }
.select { _1.attr("class").nil? || _1.attr("class") == "note" } .select { _1.attr("class").nil? || _1.attr("class") == "note" }
.reduce("") { |str, node| str + node.to_s } .reduce("") { |str, node| str + node.to_s }
check = Check.find_or_initialize_by(external_number: sc_number) check = Check.find_or_initialize_by(guideline_id: guideline.id, number: sc_number)
new_standards = Set.new(check.standards) new_standards = Set.new(check.standards)
new_standards += standards new_standards += standards
check.name_de = sc_title check.name_de = translate sc_title if check.name_de.blank?
check.name_en = sc_title check.name_en = sc_title
check.principle = principle
check.standards = new_standards check.standards = new_standards
check.applicable_to_app = check.applicable_to_web = true check.applicable_to_app = check.applicable_to_web = true
check.external_number = sc_number
check.external_url = "#{WCAG_22_EN_URL}#{sc_url}" check.external_url = "#{WCAG_22_EN_URL}#{sc_url}"
check.conformity_level = sc_level&.to_sym check.conformity_level = sc_level&.to_sym
check.conformity_notice_de = sc_conformity_notice check.conformity_notice_de = translate sc_conformity_notice
check.criterion_de = "<div>#{full_text}</div>".gsub('href="#', %(href="#{WCAG_22_EN_URL}#)) check.criterion_de = "<div>#{translate full_text}</div>".gsub('href="#', %(href="#{WCAG_22_EN_URL}#)) if check.criterion_de.blank?
qref_attrs = quick_criteria["#{principle.id}.#{guideline.number}.#{sc_number}"]
check.quick_criterion_en = qref_attrs&.dig :quick_criterion_en
check.quick_criterion_de = qref_attrs&.dig :quick_criterion_en
if qref_attrs&.dig(:link_url)
link = Link.find_or_create_by(link_category:, url: qref_attrs[:link_url])
link.update(text: "#{check.full_number} Verstehen")
check.links << link
end
if qref_attrs&.dig(:perm_url)
link = Link.find_or_create_by(link_category: qr_category, url: qref_attrs[:perm_url])
link.update(text: "#{check.full_number} Quick Reference")
check.links << link
end
# check.criterion_details_en = qref_attrs&.dig :criterion_details_en
# check.criterion_details_de = qref_attrs&.dig :criterion_details_en
check.save! check.save!
end end
end end

View file

@ -8,7 +8,7 @@ class ChecksControllerTest < ::ControllerTest
end end
setup do setup do
@principle = principles(:one) @guideline = guidelines(:one)
@check = checks(:deletable) @check = checks(:deletable)
User.create!(email_address: "test@example.com", password: "password") User.create!(email_address: "test@example.com", password: "password")
login("test@example.com", "password") login("test@example.com", "password")
@ -27,7 +27,7 @@ class ChecksControllerTest < ::ControllerTest
test "should create check" do test "should create check" do
assert_difference("Check.count") do assert_difference("Check.count") do
post checks_url, post checks_url,
params: { check: { principle_id: @principle.id, number: Check.maximum(:number) + 1, level: @check.level, name_de: @check.name_de, position: @check.position, params: { check: { guideline_id: @guideline.id, number: Check.maximum(:number) + 1, level: @check.level, name_de: @check.name_de, position: @check.position,
criterion_de: @check.criterion_de } } criterion_de: @check.criterion_de } }
end end
@ -46,7 +46,7 @@ class ChecksControllerTest < ::ControllerTest
test "should update check" do test "should update check" do
patch check_url(@check), patch check_url(@check),
params: { check: { principle_id: @principle.id, level: @check.level, name_de: @check.t_name, position: @check.position, params: { check: { guideline_id: @guideline.id, level: @check.level, name_de: @check.t_name, position: @check.position,
criterion_de: @check.criterion_de } } criterion_de: @check.criterion_de } }
assert_redirected_to check_url(@check) assert_redirected_to check_url(@check)
end end

View file

@ -0,0 +1,49 @@
require "test_helper"
class GuidelinesControllerTest < ActionDispatch::IntegrationTest
setup do
skip "login"
@guideline = guidelines(:one)
end
test "should get index" do
get guidelines_url
assert_response :success
end
test "should get new" do
get new_guideline_url
assert_response :success
end
test "should create guideline" do
assert_difference("Guideline.count") do
post guidelines_url, params: { guideline: { name_de: @guideline.name_de, number: @guideline.number, principle_id: @guideline.principle_id } }
end
assert_redirected_to guideline_url(Guideline.last)
end
test "should show guideline" do
get guideline_url(@guideline)
assert_response :success
end
test "should get edit" do
get edit_guideline_url(@guideline)
assert_response :success
end
test "should update guideline" do
patch guideline_url(@guideline), params: { guideline: { name_de: @guideline.name_de, number: @guideline.number, principle_id: @guideline.principle_id } }
assert_redirected_to guideline_url(@guideline)
end
test "should destroy guideline" do
assert_difference("Guideline.count", -1) do
delete guideline_url(@guideline)
end
assert_redirected_to guidelines_url
end
end

View file

@ -5,18 +5,18 @@ one:
name_de: MyString name_de: MyString
level: 1 level: 1
number: 1 number: 1
principle: one guideline: one
two: two:
position: MyString position: MyString
name_de: MyString name_de: MyString
level: 1 level: 1
number: 2 number: 2
principle: one guideline: one
deletable: deletable:
position: MyString position: MyString
name_de: MyString name_de: MyString
level: 1 level: 1
number: 3 number: 3
principle: one guideline: one

11
test/fixtures/guidelines.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
principle: one
number: 1
name_de: MyString
two:
principle: two
number: 1
name_de: MyString

View file

@ -0,0 +1,7 @@
require "test_helper"
class GuidelineTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View file

@ -0,0 +1,45 @@
require "application_system_test_case"
class GuidelinesTest < ApplicationSystemTestCase
setup do
@guideline = guidelines(:one)
end
test "visiting the index" do
visit guidelines_url
assert_selector "h1", text: "Guidelines"
end
test "should create guideline" do
visit guidelines_url
click_on "New guideline"
fill_in "Name de", with: @guideline.name_de
fill_in "Number", with: @guideline.number
fill_in "Principle", with: @guideline.principle_id
click_on "Create Guideline"
assert_text "Guideline was successfully created"
click_on "Back"
end
test "should update Guideline" do
visit guideline_url(@guideline)
click_on "Edit this guideline", match: :first
fill_in "Name de", with: @guideline.name_de
fill_in "Number", with: @guideline.number
fill_in "Principle", with: @guideline.principle_id
click_on "Update Guideline"
assert_text "Guideline was successfully updated"
click_on "Back"
end
test "should destroy Guideline" do
visit guideline_url(@guideline)
click_on "Destroy this guideline", match: :first
assert_text "Guideline was successfully destroyed"
end
end

View file