Repository navigation
Refuse to build a selector from a label that parameterizes to nothing - #55
Merged
Merged
Conversation
Follow-up to #53, from a review of it. `parameterize` transliterates to Latin and drops the rest, so 'Имя', '名前', '№' and a blank string all come back as ''. ActiveAdmin makes the same call, so it renders `class="row row-"` for every one of them — and #53 made the gem build `tr.row.row-` to match, which matches them all at once: attributes_row_selector('Имя') => tr.row.row- > td attributes_row_selector('Телефон') => tr.row.row- > td On a page with both rows, `have_attribute_row('Имя', exact_text: ...)` passes against the phone row, and `within_attribute_row('Имя')` raises Capybara::Ambiguous. The derivation #53 replaced produced `row-имя`, matched nothing, and failed honestly. Turning a loud failure into a silent wrong match is the worse half of the trade, so the helper now raises with the label named and a suggestion to match on text. Also here, all from the same review: * `selectors/table.rb` and `selectors/attributes_table.rb` reference `Util` without requiring it. They load today only because `capybara/active_admin.rb` happens to require util one line earlier; requiring the selectors file directly raised NameError. * `table_header_selector(nil, column: 'x')` returned the generic header selector, discarding the override — so `find_table_header` matched the first header on the page whatever column was asked for. * `table_cell_selector(:full_name)` had no example, though that is the commonest input of all and the old derivation broke it (`col-fullname`). Added, plus examples for the new guard. * Two example names still said "strips slashes" / "strips other special characters", the rule #53 deleted — they passed only because those two labels collapse the same way under both. A maintainer reading them as the contract would reinstate the stripping. * CHANGELOG had nothing for #53 at all, in a released gem whose README commits to Keep a Changelog, including the selector changes a consumer needs to know about on `bundle update`.
This was referenced Oct 1, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #53, from a
max-effort review of it. Six findings, one of them a regression #53 introduced.The regression: a silent wrong match replaced a loud failure
parameterizetransliterates to Latin and drops what it cannot, so a label with no Latin-transliterable characters comes back empty:ActiveAdmin makes the same call, so it renders
class="row row-"for every one of them. After #53 the gem built a selector to match that:One selector, every such row. On a page with both rows
have_attribute_row('Имя', exact_text: '+380501112233')passes — asserting the name row holds a phone number — andwithin_attribute_row('Имя')raisesCapybara::Ambiguous.table_cell_selectorandfind_table_cellhave the same shape.The derivation #53 replaced produced
row-имя, matched nothing, and failed honestly. Being more faithful to ActiveAdmin is worth very little if the price is a false pass in a testing gem, socss_class_for_labelnow raises instead, naming the label and pointing at the alternative:This also covers the blank-label case, where the mismatch is sharper still: ActiveAdmin's guard is
elsif title.present?, sorow('')emitsclass="row"with norow-at all, and the old selector could never have matched.Five more from the same review
Utilwas referenced without being required.selectors/table.rbandselectors/attributes_table.rbuseUtil.css_class_for_labelbut neither requires it. They load today only becausecapybara/active_admin.rbrequiresutilone line beforeselectors;require 'capybara/active_admin/selectors'on its own raisedNameError: uninitialized constant …Selectors::Table::Util.table_header_selector(nil, column: 'x')discarded the override. The early return guarded ontext.nil?while the line below normalizedoptions[:column] || text, so the override never ran andfind_table_headermatched the first header on the page whatever column was asked for. Guard is nowtext.nil? && options[:column].nil?.table_cell_selector(:full_name)had no example — the commonest input of all, and precisely what the pre-#53 derivation broke (col-fullname). Without a spec, a future "simplify this" refactor that reintroduces underscore stripping passes the whole suite.Two example names described a rule #53 deleted.
'strips slashes from column name'and'strips other special characters from column name'passed only because'Country / Region'and'Price (USD)'happen to collapse the same way under both rules —parameterizesubstitutes and collapses, it never strips. Seven lines below,'keeps a hyphen, the way parameterize does'asserts the opposite. Renamed to say what they check.CHANGELOG had no entry for #53 at all, in a released gem whose README commits to Keep a Changelog and SemVer. Added, including the selectors that change for a consumer on
bundle update—col-email→col-e-mail,col-customers_name→col-customer_s_name,col-fullname→col-full_name— and the note that an app carrying a pre-normalizing workaround should drop it.Suite
38 examples, 0 failures. The new guard is covered both ways;
require 'capybara/active_admin/selectors'in isolation now works.Not in this PR
The review found three more label→DOM-name derivations still hand-rolled, each the same bug class #53 fixed, and all pre-existing rather than introduced by it:
Util.parse_model_name—gsub(' ', '_').downcase.singularizevs ActiveModel'sunderscore.tr('/', '_').have_attributes_table(model: 'Billing::Employee')buildsdiv.attributes_table.billing::employeeand raisesNokogiri::CSS::SyntaxError; the gratuitoussingularizealso breaksStatistics→statistic.table_selector— same, againstactive_admin_config.resource_name.plural. Needs its own derivation, notparse_model_name: the table id comes from the registered resource name while the attributes-table class comes from the model class.table_scope_selector— a bareli.scope, whileActiveAdmin::Scope#initializesets@id = name.to_s.parameterize(separator: '_')and renders it as a class.have_table_scopematches onexact_text:instead, which cannot work with AA's defaultscopes_show_count, since thelitext includes the count.Worth a separate PR; happy to open it.