syrma 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b76bd8cacebc9d29ee6b28a913efb6ef5ec7283cdf6462279064343207925a71
4
- data.tar.gz: 5ee18a3987ecee83fd8c2a334afee51c1a161f3e892c155d0a9b1ecda3e0e68a
3
+ metadata.gz: be544217dc4ba67f34fa4cb6a824be44ab74dee130324f615e0824c5ab23fd72
4
+ data.tar.gz: 970b020a33399800791693b1922aacdfa679d4260947c75ff938838b31ad8794
5
5
  SHA512:
6
- metadata.gz: 8166b829a204806d7c53c101bebab2b1568e95d2af3756510aa85eecf76a9234602b656a316388ce5f0c2f09b4e949b6ba18076aaabf8b2b9e2146ba69d88348
7
- data.tar.gz: 11694f50f19407679166aa2cc305c1418832a3075166e76d9153cbd17b31996989f7211bd7b9997ba9f8818903a17147f5342196cd6d2ff4a4f7d9aa4ef6214f
6
+ metadata.gz: a2e0b783bc7e6c66fba35676fc1d10117e4c0436362eb494f32d0d5e857ff2a1ad50f2f743722fd13ae31633a6a5ba9fe9a0475d79cbf1796edf3f94a9a5f878
7
+ data.tar.gz: f9d84eba21b734e63dc793461b7f3b45fb75be5ffc862915ede839c245e269296b4dd8690c570a3cd29f9236aa02263d31dfc4b06ce278e5934f01e6c309ab9b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 - 2026-09-24
4
+
5
+ - Support Zaniah 0.7 inspection snapshots and semantic accessibility queries.
6
+ - Use Zaniah's public font, executor, and frame APIs instead of internal instrumentation.
7
+
3
8
  ## 0.3.0 - 2026-09-20
4
9
 
5
10
  - Add deterministic virtual-clock demo recording with humanized input, pixel-space chrome, and Wezen output hooks.
data/README.md CHANGED
@@ -54,7 +54,7 @@ bundle install
54
54
  bundle exec syrma doctor
55
55
  ```
56
56
 
57
- Syrma requires Ruby 3.1 or later and supports Zaniah `>= 0.2, < 0.6`. RSpec users can replace Minitest with RSpec in the test group.
57
+ Syrma requires Ruby 3.1 or later and supports Zaniah `>= 0.7, < 1.0`. RSpec users can replace Minitest with RSpec in the test group.
58
58
 
59
59
  ### Demo recording
60
60
 
@@ -135,11 +135,13 @@ end
135
135
  | Keyboard/text | `press`, `type`, `paste`, `compose`, `commit` |
136
136
  | Window/input | `resize`, `close`, `drop_files`, `feed_terminal` |
137
137
  | Synchronize | `settle`, `wait_for`, `advance` |
138
- | Inspect | `tree`, `texts`, `at`, `pixel`, `screenshot`, `terminal_lines`, `menu`, `tooltip` |
138
+ | Inspect | `tree`, `texts`, `at`, `accessibility`, `pixel`, `screenshot`, `terminal_lines`, `menu`, `tooltip` |
139
139
  | Assert | Text, element, visibility, panels, decorations, background, pixel, tooltip, menu, tree/terminal/image snapshots |
140
140
 
141
141
  Locators are lazy: every operation resolves them against the latest rendered frame. Actions wait for visibility and an unobscured matching event handler, then send events through `Window#input`.
142
142
 
143
+ `ui.accessibility(role: :button, label: "Save")` searches Zaniah's semantic tree and returns `[node, path]` pairs. Unlike visual locators, semantic nodes need not correspond to a rendered element or have bounds; use `ui.find` for pointer actions.
144
+
143
145
  Panel and decoration assertions use rendered `test_id` instrumentation, so application code does not need a Syrma or Canopus runtime dependency:
144
146
 
145
147
  ```ruby
data/lib/syrma/cli.rb CHANGED
@@ -40,7 +40,7 @@ module Syrma
40
40
  checks = {
41
41
  "Ruby" => "#{RUBY_VERSION} (#{RUBY_PLATFORM})",
42
42
  "zaniah" => Zaniah::VERSION,
43
- "Bundled font" => File.file?(Internals.bundled_font_path) ? Internals.bundled_font_path : nil,
43
+ "Bundled font" => File.file?(Zaniah.bundled_font_path) ? Zaniah.bundled_font_path : nil,
44
44
  "YJIT" => defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? ? "enabled" : "disabled",
45
45
  "External encoding" => Encoding.default_external.name
46
46
  }
data/lib/syrma/session.rb CHANGED
@@ -50,7 +50,7 @@ module Syrma
50
50
  @outputs = {}
51
51
  @windows = attached_windows || [open_window(backend, width, height, window_options, keymap)]
52
52
  @drivers = @windows.map { |window| prepare(window, backend, text, fonts) }
53
- @font_paths = text == :deterministic ? [Internals.bundled_font_path, *fonts.map { |font| File.expand_path(font) }] : []
53
+ @font_paths = text == :deterministic ? [Zaniah.bundled_font_path, *fonts.map { |font| File.expand_path(font) }] : []
54
54
  @current = 0
55
55
  mount&.call(window)
56
56
  settle
@@ -102,7 +102,7 @@ module Syrma
102
102
  candidate.tick
103
103
  busy = true
104
104
  end
105
- return self unless busy || (@app && !Internals.executor_idle?(@app.executor))
105
+ return self unless busy || (@app && !@app.executor.idle?)
106
106
  end
107
107
  raise UnstableUI, "UI did not stabilize within #{@max_frames} frames (continuous animation?)"
108
108
  end
@@ -14,10 +14,11 @@ module Syrma
14
14
  def inspect = "#<#{type}#{test_id && " @#{test_id}"}#{text && " #{text.inspect}"} #{path.join('.')}>"
15
15
  end
16
16
 
17
- Tree = Data.define(:frame, :size, :root, :nodes, :hits, :text_runs, :menu, :menu_index, :tooltip) do
17
+ Tree = Data.define(:frame, :size, :root, :nodes, :hits, :text_runs, :menu, :menu_index, :tooltip, :hit_regions) do
18
18
  def hit_at(point, event: :mouse_down, button: :left)
19
- hits.reverse_each do |bounds, node|
20
- next unless bounds.contains?(point)
19
+ hits.length.times.reverse_each do |index|
20
+ _bounds, node = hits[index]
21
+ next unless hit_regions[index].contains?(point)
21
22
 
22
23
  return node if node.nil? || Tree.handles?(node, event, button)
23
24
  end
@@ -27,7 +28,7 @@ module Syrma
27
28
  def self.handles?(node, event, button)
28
29
  case event
29
30
  when :mouse_down
30
- (button == :right && Internals.context_menu(node.element)) ||
31
+ (button == :right && node.element.respond_to?(:context_menu_items) && node.element.context_menu_items) ||
31
32
  (node.handlers & %i[mouse_down click drag]).any?
32
33
  when :mouse_move then node.handlers.include?(:hover) || node.tooltip
33
34
  when :mouse_up then node.handlers.include?(:mouse_up)
@@ -38,40 +39,43 @@ module Syrma
38
39
 
39
40
  class SnapshotBuilder
40
41
  def build(window)
42
+ snapshot = Zaniah::Inspection.snapshot(window)
41
43
  by_element = {}.compare_by_identity
42
44
  nodes = []
43
45
  viewport = Zaniah::Bounds.new(0, 0, window.content_size.width, window.content_size.height)
44
- root = window.testing_root && visit(window.testing_root, viewport, [0], nodes, by_element)
45
- hits = window.dispatcher.hits.map { |hit| [hit.bounds, hit.owner && by_element[hit.owner]] }
46
- popup = window.popup
47
- Tree.new(frame: window.testing_frame, size: window.content_size, root: root,
48
- nodes: nodes.freeze, hits: hits.freeze, text_runs: window.text_runs.map(&:dup).freeze,
46
+ root = snapshot.root && visit(snapshot.root, viewport, [0], nodes, by_element)
47
+ hits = snapshot.hits.map { |hit| [hit.bounds, hit.owner && by_element[hit.owner]] }
48
+ popup = snapshot.overlays.popup
49
+ tooltip = snapshot.overlays.tooltip
50
+ Tree.new(frame: snapshot.frame.number, size: window.content_size, root: root,
51
+ nodes: nodes.freeze, hits: hits.freeze, hit_regions: snapshot.hits, text_runs: snapshot.text_runs,
49
52
  menu: popup&.labels, menu_index: popup&.selected_index,
50
- tooltip: Internals.shown_tooltip(window))
53
+ tooltip: tooltip && tooltip[:shown] ? tooltip[:text] : nil)
51
54
  end
52
55
 
53
56
  private
54
57
 
55
- def visit(element, clip, path, nodes, by_element)
56
- bounds = element.layout_node&.bounds or return nil
57
- style = Internals.style(element)
58
+ def visit(entry, clip, path, nodes, by_element)
59
+ bounds = entry.bounds or return nil
60
+ style = entry.style
58
61
  return nil if style[:display] == :none
59
62
 
60
63
  visible = bounds.intersect(clip)
61
64
  child_clip = style[:overflow] == :visible ? clip : visible
62
65
  index = nodes.length
63
66
  nodes << nil
64
- children = element.children.each_with_index.filter_map do |child, child_index|
67
+ children = entry.children.each_with_index.filter_map do |child, child_index|
65
68
  visit(child, child_clip, path + [child_index], nodes, by_element)
66
69
  end
70
+ element = entry.element
67
71
  node = Node.new(
68
- path: path.freeze, element: element, type: element.class.name.split("::").last.downcase.to_sym,
69
- test_id: element.test_id, key: Internals.key(element),
72
+ path: path.freeze, element: element, type: entry.type.split("::").last.downcase.to_sym,
73
+ test_id: entry.test_id, key: entry.key,
70
74
  text: text_attr(element, :text), color: text_attr(element, :text_color),
71
75
  font_size: text_attr(element, :font_size), bounds: bounds, visible_bounds: visible,
72
- background: Internals.background(element), border_color: Internals.border_color(element),
73
- radius: Internals.radius(element), handlers: element.handlers,
74
- tooltip: Internals.tooltip(element), children: children.freeze
76
+ background: style[:background], border_color: style[:border_color],
77
+ radius: style[:corner_radii], handlers: entry.handlers,
78
+ tooltip: entry.tooltip, children: children.freeze
75
79
  )
76
80
  nodes[index] = node
77
81
  by_element[element] = node
@@ -9,7 +9,7 @@ module Syrma
9
9
  when :none, nil then nil
10
10
  when :native then Zaniah::TextSystem::Renderer.new
11
11
  when :deterministic
12
- paths = [Internals.bundled_font_path, *fonts.map { |font| File.expand_path(font) }]
12
+ paths = [Zaniah.bundled_font_path, *fonts.map { |font| File.expand_path(font) }]
13
13
  missing = paths.reject { |path| File.file?(path) }
14
14
  raise ArgumentError, "Fonts not found: #{missing.join(', ')}" unless missing.empty?
15
15
 
data/lib/syrma/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syrma
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -17,7 +17,7 @@ module Syrma
17
17
 
18
18
  def tree
19
19
  session.settle
20
- @tree = nil if @tree && @tree.frame != window.testing_frame
20
+ @tree = nil if @tree && @tree.frame != window.frame_number
21
21
  @tree ||= @builder.build(window)
22
22
  end
23
23
 
@@ -27,6 +27,10 @@ module Syrma
27
27
  def text(value) = find(text: value)
28
28
  def texts = tree.text_runs.map { |run| run[2] }
29
29
  def tooltip = tree.tooltip
30
+ def accessibility(role: nil, label: nil, states: {})
31
+ session.settle
32
+ Zaniah::Inspection.snapshot(window).accessibility.query(role: role, label: label, states: states)
33
+ end
30
34
 
31
35
  def button(label)
32
36
  find(clickable: true) do |node|
@@ -182,7 +186,7 @@ module Syrma
182
186
  def menu = (@menu_driver ||= PopupDriver.new(self))
183
187
 
184
188
  def dispatch(event)
185
- session.event_log.add(window.testing_frame, window.title, event)
189
+ session.event_log.add(window.frame_number, window.title, event)
186
190
  window.input(event)
187
191
  session.settle unless @in_gesture && session.event_frames == :gesture
188
192
  event
data/lib/syrma.rb CHANGED
@@ -4,7 +4,6 @@ require "zaniah"
4
4
  require_relative "syrma/version"
5
5
  require_relative "syrma/configuration"
6
6
  require_relative "syrma/errors"
7
- require_relative "syrma/internals"
8
7
  require_relative "syrma/instrumentation"
9
8
  require_relative "syrma/clock"
10
9
  require_relative "syrma/text_systems"
data/sig/syrma.rbs CHANGED
@@ -78,6 +78,7 @@ module Syrma
78
78
  attr_reader root: Node?
79
79
  attr_reader nodes: Array[Node]
80
80
  attr_reader hits: Array[untyped]
81
+ attr_reader hit_regions: Array[untyped]
81
82
  attr_reader text_runs: Array[untyped]
82
83
  attr_reader menu: Array[String]?
83
84
  attr_reader menu_index: Integer?
@@ -200,6 +201,7 @@ module Syrma
200
201
  def text: (String | Regexp value) -> Locator
201
202
  def texts: () -> Array[String]
202
203
  def tooltip: () -> String?
204
+ def accessibility: (?role: Symbol?, ?label: String | Regexp | nil, ?states: Hash[Symbol, untyped]) -> Array[[untyped, Array[Integer]]]
203
205
  def button: (String label) -> Locator
204
206
  def at: (untyped x, ?untyped y, ?event: Symbol, ?button: Symbol) -> Node?
205
207
  def pixel: (Integer x, Integer y) -> Array[Integer]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syrma
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-20 00:00:00.000000000 Z
11
+ date: 2026-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zaniah
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.2'
19
+ version: '0.7'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '0.6'
22
+ version: '1.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '0.2'
29
+ version: '0.7'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.6'
32
+ version: '1.0'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: wezen
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -73,7 +73,6 @@ files:
73
73
  - lib/syrma/event_codec.rb
74
74
  - lib/syrma/event_log.rb
75
75
  - lib/syrma/instrumentation.rb
76
- - lib/syrma/internals.rb
77
76
  - lib/syrma/minitest.rb
78
77
  - lib/syrma/popup_driver.rb
79
78
  - lib/syrma/query.rb
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Syrma
4
- module Internals
5
- module_function
6
-
7
- def style(element) = element.instance_variable_get(:@style) || {}
8
- def background(element) = paint_value(element, :background, :@background)
9
- def border_color(element) = paint_value(element, :border_color, :@border_color)
10
- def radius(element) = paint_value(element, :corner_radii, :@radius)
11
- def tooltip(element) = element.instance_variable_get(:@tooltip)
12
- def context_menu(element) = element.instance_variable_get(:@context_menu)
13
- def key(element) = element.instance_variable_get(:@key)
14
- def shown_tooltip(window)
15
- tip = window.instance_variable_get(:@tooltip)
16
- tip && tip[:shown] ? tip[:text] : nil
17
- end
18
-
19
- def bundled_font_path
20
- _, entry = $LOAD_PATH.resolve_feature_path("zaniah")
21
- File.expand_path("../assets/fonts/Abel-Regular.ttf", File.dirname(entry))
22
- end
23
-
24
- def executor_idle?(executor) = executor.instance_variable_get(:@foreground).empty?
25
-
26
- def paint_value(element, property, legacy)
27
- resolved = element.respond_to?(:resolved_style) ? element.resolved_style : nil
28
- resolved&.[](property) || element.instance_variable_get(legacy) || style(element)[property]
29
- end
30
- private_class_method :paint_value
31
- end
32
- end