diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 02c7971..a6aa322 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -57,6 +57,11 @@ } /* NOTE: move to higher priority layer instead of using !important?; add CSS * @layer requirements in README */ +/* Disable elements with: + * `[aria-disabled=true]` - non-form elements (e.g. links), + * `[disabled]` - form controls, that may be enabled conditionally, + * `[readonly]` - form controls, that stay disabled for form lifetime. */ +[aria-disabled=true], [disabled] { border-color: var(--color-border-gray) !important; color: var(--color-border-gray) !important; diff --git a/app/javascript/application.js b/app/javascript/application.js index 09ced32..8318cd9 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -50,21 +50,30 @@ function formValidate(event) { window.formValidate = formValidate -/* Turbo stream actions */ +// Turbo stream actions. +const FORM_CONTROLS = ["BUTTON", "FIELDSET", "INPUT", "OPTGROUP", "OPTION", "SELECT", + "TEXTAREA"] Turbo.StreamElement.prototype.disableElement = function(element) { + if (FORM_CONTROLS.includes(element.tagName)) { element.setAttribute("disabled", "disabled") + } else { element.setAttribute("aria-disabled", "true") + // NOTE: tabindex should not be disabled? element.setAttribute("tabindex", "-1") + } } Turbo.StreamActions.disable = function() { this.targetElements.forEach((e) => { this.disableElement(e) }) } Turbo.StreamElement.prototype.enableElement = function(element) { + if (FORM_CONTROLS.includes(element.tagName)) { element.removeAttribute("disabled") + } else { element.removeAttribute("aria-disabled") - // Assume 'tabindex' is not used explicitly, so removing it is safe + // Assume 'tabindex' is not used explicitly, so removing it is safe. element.removeAttribute("tabindex") + } } Turbo.StreamActions.enable = function() { this.targetElements.forEach((e) => { this.enableElement(e) }) diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index ddcf474..57f9ad3 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -67,21 +67,11 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase ActiveRecord::Base.establish_connection(TEST_CONFIGS[name.to_sym]) end - #def assert_stale(element) - # assert_raises(Selenium::WebDriver::Error::StaleElementReferenceError) { element.tag_name } - #end + Capybara.modify_selector(:link) do + expression_filter(:disabled) do |xpath, value| + builder(xpath).add_attribute_conditions('aria-disabled': value) + end + end - # HTML does not allow [disabled] attribute on tag, so it's not possible to - # easily find them using e.g. :link selector - #Capybara.add_selector(:disabled_link) do - # label " tag with [disabled] attribute" - #end - #test "click disabled link" do - # Link should be unclickable - # assert_raises(Selenium::WebDriver::Error::ElementClickInterceptedError) do - # # Use custom selector for disabled links - # find('a[disabled]').click - # end - #end end diff --git a/test/system/units_test.rb b/test/system/units_test.rb index 63862f6..dea2479 100644 --- a/test/system/units_test.rb +++ b/test/system/units_test.rb @@ -41,9 +41,10 @@ class UnitsTest < ApplicationSystemTestCase sign_in link_labels.slice!(:new_unit, :new_subunit) type, label = link_labels.to_a.sample - new_link = all(:link, exact_text: label).sample - new_link.click - assert_equal 'disabled', new_link[:disabled] + all(:link, exact_text: label).sample.then do |link| + link.click + link.assert_matches_selector :link, disabled: true + end values = nil within 'tbody > tr:has(input[type=text], textarea)' do @@ -74,7 +75,7 @@ class UnitsTest < ApplicationSystemTestCase assert_no_selector :fillable_field assert_selector 'tr', count: @user.units.count end - assert_no_selector :element, :a, 'disabled': 'disabled', + assert_no_selector :link, disabled: true, exact_text: Regexp.union(link_labels.values) assert_equal values, Unit.last.attributes.slice(*values.keys) end @@ -126,7 +127,7 @@ class UnitsTest < ApplicationSystemTestCase if type == :edit assert_no_selector :link, exact_text: link[:text] else - assert_equal 'disabled', link[:disabled] + link.assert_matches_selector :link, disabled: true end within 'tbody > tr:has(input[type=text])' do @@ -159,7 +160,7 @@ class UnitsTest < ApplicationSystemTestCase refute subunit_link&.visible? links[:new_subunit].delete(subunit_link) else - assert link[:disabled] + link.assert_matches_selector :link, disabled: true end type, link = random_link[] diff --git a/test/system/users_test.rb b/test/system/users_test.rb index a90bce7..5b281ca 100644 --- a/test/system/users_test.rb +++ b/test/system/users_test.rb @@ -8,7 +8,7 @@ class UsersTest < ApplicationSystemTestCase test 'sign in' do visit root_url - assert find_link(href: new_user_session_path)[:disabled] + assert_selector :link, text: t(:sign_in), disabled: true sign_in assert_no_current_path new_user_session_path @@ -27,7 +27,7 @@ class UsersTest < ApplicationSystemTestCase assert_current_path new_user_session_path assert_text t('devise.failure.invalid', authentication_keys: label.downcase_first) - assert find_link(href: new_user_session_path)[:disabled] + assert_selector :link, text: t(:sign_in), disabled: true assert has_field?(label, with: email) end @@ -89,7 +89,7 @@ class UsersTest < ApplicationSystemTestCase test 'register' do visit root_url click_on t(:register) - assert find_link(href: new_user_registration_path)[:disabled] + assert_selector :link, text: t(:register), disabled: true fill_in User.human_attribute_name(:email), with: random_email password = random_password