Module: Dommy::Rails::AriaSnapshotMatching

Defined in:
lib/dommy/rails/aria_snapshot_matching.rb

Overview

Compares a Playwright-compatible ARIA snapshot against an expected template using Playwright’s ‘toMatchAriaSnapshot` semantics: a SUBSET match. Every node listed in `expected` must appear in `actual`, in order, at the same nesting — but `actual` may contain extra nodes. A node matches when role + (optional) name + (subset of) flags agree. An expected name written as `/pattern/` is matched as a regular expression; an omitted name is a wildcard.

Defined Under Namespace

Classes: Node

Class Method Summary collapse

Class Method Details

.children_match?(expected_children, actual_children) ⇒ Boolean

Greedy ordered-subsequence match: each expected child must match a later actual child, allowing extra actual children in between.

Returns:

  • (Boolean)


40
41
42
43
44
45
46
47
48
49
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 40

def children_match?(expected_children, actual_children)
  cursor = 0
  expected_children.each do |expected|
    cursor += 1 until cursor >= actual_children.size || node_match?(expected, actual_children[cursor])
    return false if cursor >= actual_children.size

    cursor += 1
  end
  true
end

.matches?(actual_text, expected_text) ⇒ Boolean

Returns:

  • (Boolean)


17
18
19
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 17

def matches?(actual_text, expected_text)
  children_match?(parse(expected_text).children, parse(actual_text).children)
end

.name_match?(expected, actual) ⇒ Boolean

Returns:

  • (Boolean)


31
32
33
34
35
36
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 31

def name_match?(expected, actual)
  return actual.name.to_s.match?(expected.name_regex) if expected.name_regex
  return true if expected.name.nil?

  expected.name == actual.name
end

.node_match?(expected, actual) ⇒ Boolean

— comparison —

Returns:

  • (Boolean)


23
24
25
26
27
28
29
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 23

def node_match?(expected, actual)
  return false unless expected.role == actual.role
  return false unless name_match?(expected, actual)
  return false unless (expected.flags - actual.flags).empty?

  children_match?(expected.children, actual.children)
end

.parse(text) ⇒ Object

— parsing (indentation outline -> Node tree) —



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 53

def parse(text)
  root = Node.new(role: nil, flags: [], children: [])
  stack = [[-1, root]]
  text.to_s.each_line do |line|
    stripped = line.strip
    next if stripped.empty? || !stripped.start_with?("- ")

    indent = line[/\A */].length
    node = parse_line(stripped[2..])
    stack.pop while stack.size > 1 && stack.last[0] >= indent
    stack.last[1].children << node
    stack.push([indent, node])
  end
  root
end

.parse_line(body) ⇒ Object



69
70
71
72
73
74
75
76
77
78
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 69

def parse_line(body)
  body = body.sub(/:\s*\z/, "")
  return Node.new(role: "text", name: unquote(body.sub(/\Atext:\s*/, "")), flags: [], children: []) \
    if body.start_with?("text:")

  role, rest = body.split(/\s+/, 2)
  name, name_regex, rest = scan_name(rest)
  flags = rest.to_s.scan(/\[([^\]]*)\]/).flatten
  Node.new(role: role, name: name, name_regex: name_regex, flags: flags, children: [])
end

.scan_name(rest) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 80

def scan_name(rest)
  return [nil, nil, rest] if rest.nil?

  if rest.start_with?('"') && (md = rest.match(/\A"((?:\\.|[^"\\])*)"/))
    [unescape(md[1]), nil, rest[md.end(0)..].to_s.strip]
  elsif rest.start_with?("/") && (md = rest.match(%r{\A/((?:\\.|[^/\\])*)/}))
    [nil, Regexp.new(md[1]), rest[md.end(0)..].to_s.strip]
  else
    [nil, nil, rest]
  end
end

.unescape(text) ⇒ Object



97
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 97

def unescape(text) = text.gsub(/\\(.)/, '\1')

.unquote(text) ⇒ Object



92
93
94
95
# File 'lib/dommy/rails/aria_snapshot_matching.rb', line 92

def unquote(text)
  md = text.to_s.strip.match(/\A"((?:\\.|[^"\\])*)"\z/)
  md ? unescape(md[1]) : text.to_s.strip
end