interfacets 0.9.9 → 0.9.99

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.tmp +5 -0
  3. data/Rakefile +4 -0
  4. data/lib/interfacets/client/assets.rb +1 -0
  5. data/lib/interfacets/client/bus.rb +5 -1
  6. data/lib/interfacets/client/channels/api.rb +10 -3
  7. data/lib/interfacets/client/registry.rb +9 -70
  8. data/lib/interfacets/client/utils/mruby_patches.rb +1 -1
  9. data/lib/interfacets/client/utils/open_struct.rb +90 -77
  10. data/lib/interfacets/component_registry.rb +115 -0
  11. data/lib/interfacets/component_schema_parser.rb +84 -0
  12. data/lib/interfacets/server/api.rb +5 -25
  13. data/lib/interfacets/server/assets/facet.rb +5 -3
  14. data/lib/interfacets/server/assets.rb +13 -7
  15. data/lib/interfacets/server/basic_router.rb +15 -10
  16. data/lib/interfacets/server/bus.rb +4 -9
  17. data/lib/interfacets/server/config.rb +1 -1
  18. data/lib/interfacets/server/registry.rb +23 -184
  19. data/lib/interfacets/shared/basic_routable.rb +45 -0
  20. data/lib/interfacets/shared/entities/bus.rb +41 -29
  21. data/lib/interfacets/shared/entities/specs/handlers.rb +16 -0
  22. data/lib/interfacets/shared/entities/specs.rb +46 -9
  23. data/lib/interfacets/shared/entity.rb +23 -99
  24. data/lib/interfacets/shared/entity_dsl.rb +154 -0
  25. data/lib/interfacets/shared/facet.rb +200 -0
  26. data/lib/interfacets/shared/generated_store.rb +6 -2
  27. data/lib/interfacets/shared/utils.rb +1 -1
  28. data/lib/interfacets/shared/validations.rb +5 -1
  29. data/lib/interfacets/{client → shared}/view.rb +33 -6
  30. data/lib/interfacets/test/component_registry.rb +63 -0
  31. data/lib/interfacets/test/js/inline_bus.rb +21 -12
  32. data/lib/interfacets/test/js/nodo_bus.rb +18 -1
  33. data/lib/interfacets/test/js/receivers/api.rb +14 -3
  34. data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
  35. data/lib/interfacets/test/js/receivers/react/node.rb +29 -63
  36. data/lib/interfacets/test/js/receivers/react.rb +4 -3
  37. data/lib/interfacets/test/js/receivers/timer.rb +77 -0
  38. data/lib/interfacets/test/js/receivers/url.rb +5 -0
  39. data/lib/interfacets/test/standard_elements.yml +173 -0
  40. data/lib/interfacets/test/{browser.rb → ui_simulator.rb} +18 -6
  41. data/lib/interfacets/test/validation_engine.rb +151 -0
  42. data/lib/interfacets/test.rb +0 -4
  43. data/lib/interfacets/version.rb +1 -1
  44. data/lib/interfacets.rb +3 -0
  45. metadata +29 -18
  46. data/lib/interfacets/client/facet.rb +0 -26
  47. data/lib/interfacets/client/facet2.rb +0 -15
  48. data/lib/interfacets/client/facets/attributes/accessor.rb +0 -28
  49. data/lib/interfacets/client/facets/attributes/association.rb +0 -50
  50. data/lib/interfacets/client/facets/attributes/bind.rb +0 -25
  51. data/lib/interfacets/client/facets/attributes/collection.rb +0 -47
  52. data/lib/interfacets/client/facets/attributes/readonly.rb +0 -19
  53. data/lib/interfacets/client/facets/deserializer.rb +0 -30
  54. data/lib/interfacets/client/facets/schema/deserializer.rb +0 -63
  55. data/lib/interfacets/client/facets/schema.rb +0 -63
  56. data/lib/interfacets/client/facets/serializer.rb +0 -18
  57. data/lib/interfacets/server/basic_routable.rb +0 -40
  58. data/lib/interfacets/server/facet.rb +0 -51
  59. data/lib/interfacets/shared/entity_collection.rb +0 -88
@@ -19,7 +19,7 @@ module Interfacets
19
19
  type: "interfacets:system:create_bus",
20
20
  payload: {
21
21
  id: "default",
22
- channel_ids: ["interfacets:api", "dom", "url"],
22
+ channel_ids: ["interfacets:api", "dom", "url", "timer"],
23
23
  hydration: {
24
24
  destination: { bus: "default", channel: "interfacets:api" },
25
25
  type: "interfacets:api:hydrate",
@@ -30,6 +30,10 @@ module Interfacets
30
30
  }))
31
31
  end
32
32
 
33
+ def dispatch(channel_id, event)
34
+ dispatch_to_bus(bus_id: "default", channel_id: channel_id, event: event)
35
+ end
36
+
33
37
  private
34
38
 
35
39
  def handle(event)
@@ -40,21 +44,33 @@ module Interfacets
40
44
 
41
45
  bus_event.fetch("payload").each do |channel_event|
42
46
  channel_id = channel_event.fetch("id")
43
- receiver_index
44
- .fetch(channel_id)
47
+ receiver = receiver_index.fetch(channel_id)
48
+
49
+ receiver
45
50
  .receive(
46
51
  payload: channel_event.fetch("payload"),
47
52
  dispatch: ->(ev) {
48
- dispatch(bus_id:, channel_id:, event: ev)
53
+ dispatch_to_bus(bus_id:, channel_id:, event: ev)
49
54
  },
50
55
  )
51
56
  end
52
57
  else
53
58
  raise("unhandled event type: #{event}")
54
59
  end
60
+
61
+ # Flush any responses that may have come from the server
62
+ # should separate this out so that auto-flush or manual-flush
63
+ # is possible to simulate ordering events as needed.
64
+ receiver_index.each do |channel_id, receiver|
65
+ if receiver.respond_to?(:response_queue)
66
+ receiver.flush_responses.each do |response|
67
+ dispatch_to_bus(bus_id:, channel_id:, event: response)
68
+ end
69
+ end
70
+ end
55
71
  end
56
72
 
57
- def dispatch(bus_id:, channel_id:, event:)
73
+ def dispatch_to_bus(bus_id:, channel_id:, event:)
58
74
  client.handle(
59
75
  H.j(
60
76
  {
@@ -69,13 +85,6 @@ module Interfacets
69
85
  def client
70
86
  @client ||= (
71
87
  $asset_logger = Logger.new("/dev/null")
72
- original_verbose = $VERBOSE
73
- $VERBOSE = nil
74
- begin
75
- Client::Assets.bootstrap(client_system_json.fetch("assets"))
76
- ensure
77
- $VERBOSE = original_verbose
78
- end
79
88
  Client::System.logger = $asset_logger
80
89
 
81
90
  Client.start(
@@ -30,10 +30,27 @@ module Interfacets
30
30
 
31
31
  def render
32
32
  receiver_index.each do |id, ch|
33
+ state = js_get_state(id)
34
+ next if state.nil?
35
+
33
36
  ch.receive(
34
- payload: js_get_state(id).fetch("data"),
37
+ payload: state.fetch("data"),
35
38
  dispatch: ->(e) { dispatch(id, e) }
36
39
  )
40
+
41
+ receiver_index.each do |id, ch|
42
+ if ch.respond_to?(:response_queue)
43
+ ch.flush_responses.each do |response|
44
+ dispatch(id, response)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ if js_get_logs.any? { _1.fetch("type") == "error" }
51
+ js_get_logs
52
+ .map { _1.fetch("value") }
53
+ .then { raise(_1.join("\n")) }
37
54
  end
38
55
  end
39
56
 
@@ -25,10 +25,21 @@ module Interfacets
25
25
  return if payload.dig("streams", "default").nil?
26
26
  return if payload.dig("streams", "default").empty?
27
27
 
28
+ method = payload.dig("streams", "default", "method")
28
29
  url = payload.dig("streams", "default", "url")
29
- payload
30
- .dig("streams", "default", "body", "event", "payload")
31
- .then { response_queue << router.call(url).handle(_1) }
30
+
31
+
32
+ if method == "get"
33
+ response_queue << router.call(url).render
34
+ else
35
+ payload
36
+ .dig("streams", "default", "body", "event", "payload")
37
+ .then { response_queue << router.call(url).handle(_1) }
38
+ end
39
+ end
40
+
41
+ def flush_responses
42
+ response_queue.tap { @response_queue = [] }
32
43
  end
33
44
  end
34
45
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Interfacets
6
+ module Test
7
+ module Js
8
+ module Receivers
9
+ class React
10
+ class Node
11
+ class XmlParser
12
+ attr_reader :json, :validation_engine
13
+ def initialize(json, validation_engine:)
14
+ @json = json
15
+ @validation_engine = validation_engine
16
+ end
17
+
18
+ def call
19
+ xml = Nokogiri::XML.fragment("<Facet/>")
20
+ json
21
+ .dig("streams", "default", "dom")
22
+ .map { parse_element(_1) }
23
+ .each { xml.add_child(_1) }
24
+ xml
25
+ end
26
+
27
+ private
28
+
29
+ def parse_attribute(value, top: true)
30
+ case value
31
+ when Array
32
+ when Hash
33
+ value
34
+ .transform_values { parse_attribute(_1, top: false) }
35
+ # we don't want to call to_json on nested hashes
36
+ .then { top ? _1.to_json : _1 }
37
+ when Numeric, String, TrueClass, FalseClass, NilClass
38
+ value
39
+ else
40
+ raise "unknown attribute type: #{value}"
41
+ end
42
+ end
43
+
44
+ def parse_element(el)
45
+ case el.fetch("type")
46
+ when "interfacets:string-node"
47
+ el.dig("attributes", "value").to_s
48
+ when "interfacets:react-dom:element"
49
+ xml = (
50
+ el
51
+ .fetch("element")
52
+ .tap { validation_engine&.validate_props(_1, el.fetch("attributes")) }
53
+ .then { Nokogiri::XML.fragment("<#{_1} />").children.first }
54
+ )
55
+
56
+ el.fetch("children").each do |child|
57
+ xml.add_child(parse_element(child)) if child
58
+ end
59
+
60
+ el.fetch("attributes").each do |name, value|
61
+ xml.set_attribute(name, parse_attribute(value))
62
+ end
63
+
64
+ xml
65
+ else
66
+ raise "unknown type: #{el.fetch("type")}"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -8,71 +8,17 @@ module Interfacets
8
8
  module Receivers
9
9
  class React
10
10
  class Node
11
- class XmlParser
12
- attr_reader :json
13
- def initialize(json)
14
- @json = json
15
- end
16
-
17
- def call
18
- xml = Nokogiri::XML.fragment("<Facet/>")
19
- json
20
- .dig("streams", "default", "dom")
21
- .map { parse_element(_1) }
22
- .each { xml.add_child(_1) }
23
- xml
24
- end
25
-
26
- private
27
-
28
- def parse_attribute(value)
29
- case value
30
- when Array
31
- when Hash
32
- value.transform_values { parse_attribute(_1) }
33
- when Numeric, String, TrueClass, FalseClass, NilClass
34
- value
35
- else
36
- raise "unknown attribute type: #{value}"
37
- end
38
- end
39
-
40
- def parse_element(el)
41
- case el.fetch("type")
42
- when "interfacets:string-node"
43
- el.dig("attributes", "value")
44
- when "interfacets:react-dom:element"
45
- xml = (
46
- el
47
- .fetch("element")
48
- .then { Nokogiri::XML.fragment("<#{_1} />").children.first }
49
- )
50
-
51
- el.fetch("children").each do |child|
52
- xml.add_child(parse_element(child)) if child
53
- end
54
-
55
- el.fetch("attributes").each do |name, value|
56
- xml.set_attribute(name, parse_attribute(value).to_json)
57
- end
58
-
59
- xml
60
- else
61
- raise "unknown type: #{el.fetch("type")}"
62
- end
63
- end
64
- end
65
-
66
- def self.parse(json:, dispatch:)
11
+ def self.parse(json:, dispatch:, validation_engine:)
67
12
  new(
68
- xml: XmlParser.new(json).call,
13
+ xml: XmlParser.new(json, validation_engine:).call,
69
14
  dispatch:,
15
+ validation_engine:,
70
16
  parent: nil,
71
17
  )
72
18
  end
73
19
 
74
20
  Error = Class.new(StandardError)
75
- NoMatchesErrorError = Class.new(Error)
21
+ NoMatchesError = Class.new(Error)
76
22
  MultipleMatchesError = Class.new(Error)
77
23
  StaleNodeError = Class.new(Error) do
78
24
  def initialize
@@ -87,11 +33,12 @@ module Interfacets
87
33
  end
88
34
  end
89
35
 
90
- attr_reader :xml, :dispatch, :parent
91
- def initialize(xml:, dispatch:, parent:)
36
+ attr_reader :xml, :dispatch, :parent, :validation_engine
37
+ def initialize(xml:, dispatch:, parent:, validation_engine:)
92
38
  @xml = xml
93
39
  @dispatch = dispatch
94
40
  @parent = parent
41
+ @validation_engine = validation_engine
95
42
  end
96
43
 
97
44
  def stale!
@@ -102,6 +49,20 @@ module Interfacets
102
49
  @stale || parent&.stale?
103
50
  end
104
51
 
52
+ def component_name
53
+ raise StaleNodeError if stale?
54
+
55
+ xml.name
56
+ end
57
+
58
+ def props
59
+ raise StaleNodeError if stale?
60
+
61
+ xml.attributes.transform_values do |attr|
62
+ attr.value.then { JSON.parse(_1) rescue _1 }
63
+ end
64
+ end
65
+
105
66
  def content
106
67
  raise StaleNodeError if stale?
107
68
 
@@ -129,12 +90,12 @@ module Interfacets
129
90
 
130
91
  xml
131
92
  .css(*a, **p)
132
- .map { Node.new(xml: _1, dispatch:, parent: self) }
93
+ .map { Node.new(xml: _1, dispatch:, parent: self, validation_engine:) }
133
94
  .select {
134
95
  (
135
96
  content == EMPTY_ARG || (
136
97
  if content.is_a?(Regexp)
137
- _1.content.match(content)
98
+ _1.content.match?(content)
138
99
  else
139
100
  _1.content == content
140
101
  end
@@ -149,13 +110,18 @@ module Interfacets
149
110
 
150
111
  xml
151
112
  .attribute(name)
152
- .then { JSON.parse(_1) }
113
+ &.value
114
+ &.then { JSON.parse(_1) rescue _1 }
153
115
  end
154
116
 
155
117
  def trigger(name, data = {})
156
118
  raise StaleNodeError if stale?
157
119
 
120
+ validation_engine&.validate_event(xml.name, name, data)
121
+
158
122
  value = attribute(name.to_s)
123
+ raise "No event handler registered for: #{name}" unless value
124
+
159
125
  value["payload"]["event"] = data
160
126
  dispatch.(value)
161
127
  end
@@ -8,9 +8,10 @@ module Interfacets
8
8
  module Js
9
9
  module Receivers
10
10
  class React
11
- attr_reader :name, :node
12
- def initialize(name:)
11
+ attr_reader :name, :node, :validation_engine
12
+ def initialize(name:, validation_engine: nil)
13
13
  @name = name
14
+ @validation_engine = validation_engine
14
15
  @actions = {}
15
16
  end
16
17
 
@@ -18,7 +19,7 @@ module Interfacets
18
19
  @dispatch = dispatch
19
20
  @actions = nil
20
21
  @node&.stale!
21
- @node = Node.parse(json: payload, dispatch:)
22
+ @node = Node.parse(json: payload, dispatch:, validation_engine:)
22
23
  end
23
24
 
24
25
  def handler
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Test
5
+ module Js
6
+ module Receivers
7
+ class Timer
8
+ include Enumerable
9
+
10
+ class Registration
11
+ attr_reader :ms, :response_payload, :dispatch
12
+
13
+ def initialize(ms:, response_payload:, dispatch:)
14
+ @ms = ms
15
+ @response_payload = response_payload
16
+ @dispatch = dispatch
17
+ @notified = false
18
+ end
19
+
20
+ def notify
21
+ return if @notified
22
+ @dispatch.call({ payload: response_payload })
23
+ @notified = true
24
+ end
25
+ end
26
+
27
+ attr_reader :name, :registrations
28
+ def initialize(name:)
29
+ @name = name
30
+ @registrations = []
31
+ end
32
+
33
+ def receive(payload:, dispatch:)
34
+ @dispatch = dispatch
35
+
36
+ return if payload.nil?
37
+ return if payload.empty?
38
+ return if payload.dig("streams", "default").nil?
39
+ return if payload.dig("streams", "default").empty?
40
+
41
+ event = payload.dig("streams", "default")
42
+ ms = event["ms"]
43
+ response_payload = event["response"]
44
+
45
+ return unless ms && response_payload
46
+
47
+ @registrations << Registration.new(
48
+ ms: ms,
49
+ response_payload: response_payload,
50
+ dispatch: dispatch
51
+ )
52
+ end
53
+
54
+ def handler
55
+ self
56
+ end
57
+
58
+ def first
59
+ registrations.first
60
+ end
61
+
62
+ def last
63
+ registrations.last
64
+ end
65
+
66
+ def each(&block)
67
+ registrations.each(&block)
68
+ end
69
+
70
+ def clear
71
+ @registrations = []
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -32,6 +32,7 @@ module Interfacets
32
32
  attr_reader :server, :name, :response_queue
33
33
  def initialize(name:)
34
34
  @name = name
35
+ @response_queue = []
35
36
  end
36
37
 
37
38
  def receive(payload:, dispatch:)
@@ -48,6 +49,10 @@ module Interfacets
48
49
  def handler
49
50
  @handler
50
51
  end
52
+
53
+ def flush_responses
54
+ response_queue.tap { @response_queue = [] }
55
+ end
51
56
  end
52
57
  end
53
58
  end
@@ -0,0 +1,173 @@
1
+ ---
2
+ - a
3
+ - abbr
4
+ - address
5
+ - area
6
+ - article
7
+ - aside
8
+ - audio
9
+ - b
10
+ - base
11
+ - bdi
12
+ - bdo
13
+ - blockquote
14
+ - body
15
+ - br
16
+ - button
17
+ - canvas
18
+ - caption
19
+ - cite
20
+ - code
21
+ - col
22
+ - colgroup
23
+ - data
24
+ - datalist
25
+ - dd
26
+ - del
27
+ - details
28
+ - dfn
29
+ - dialog
30
+ - div
31
+ - dl
32
+ - dt
33
+ - em
34
+ - embed
35
+ - fieldset
36
+ - figcaption
37
+ - figure
38
+ - footer
39
+ - form
40
+ - h1
41
+ - h2
42
+ - h3
43
+ - h4
44
+ - h5
45
+ - h6
46
+ - head
47
+ - header
48
+ - hgroup
49
+ - hr
50
+ - html
51
+ - i
52
+ - iframe
53
+ - img
54
+ - input
55
+ - ins
56
+ - kbd
57
+ - label
58
+ - legend
59
+ - li
60
+ - link
61
+ - main
62
+ - map
63
+ - mark
64
+ - menu
65
+ - meta
66
+ - meter
67
+ - nav
68
+ - noscript
69
+ - object
70
+ - ol
71
+ - optgroup
72
+ - option
73
+ - output
74
+ - p
75
+ - picture
76
+ - pre
77
+ - progress
78
+ - q
79
+ - rp
80
+ - rt
81
+ - ruby
82
+ - s
83
+ - samp
84
+ - script
85
+ - section
86
+ - select
87
+ - slot
88
+ - small
89
+ - source
90
+ - span
91
+ - strong
92
+ - style
93
+ - sub
94
+ - summary
95
+ - sup
96
+ - table
97
+ - tbody
98
+ - td
99
+ - template
100
+ - textarea
101
+ - tfoot
102
+ - th
103
+ - thead
104
+ - time
105
+ - title
106
+ - tr
107
+ - track
108
+ - u
109
+ - ul
110
+ - var
111
+ - video
112
+ - wbr
113
+ - animate
114
+ - animatemotion
115
+ - animatetransform
116
+ - circle
117
+ - clippath
118
+ - defs
119
+ - desc
120
+ - discard
121
+ - ellipse
122
+ - feblend
123
+ - fecolormatrix
124
+ - fecomponenttransfer
125
+ - fecomposite
126
+ - feconvolvematrix
127
+ - fediffuselighting
128
+ - fedisplacementmap
129
+ - fedistantlight
130
+ - fedropshadow
131
+ - feflood
132
+ - fefunca
133
+ - fefuncb
134
+ - fefuncg
135
+ - fefuncr
136
+ - fegaussianblur
137
+ - feimage
138
+ - femerge
139
+ - femergenode
140
+ - femorphology
141
+ - feoffset
142
+ - fepointlight
143
+ - fespecularlighting
144
+ - fespotlight
145
+ - fetile
146
+ - feturbulence
147
+ - filter
148
+ - foreignobject
149
+ - g
150
+ - image
151
+ - line
152
+ - lineargradient
153
+ - marker
154
+ - mask
155
+ - metadata
156
+ - mpath
157
+ - path
158
+ - pattern
159
+ - polygon
160
+ - polyline
161
+ - radialgradient
162
+ - rect
163
+ - script
164
+ - set
165
+ - stop
166
+ - svg
167
+ - switch
168
+ - symbol
169
+ - text
170
+ - textpath
171
+ - tspan
172
+ - use
173
+ - view
@@ -1,14 +1,15 @@
1
1
 
2
2
  module Interfacets
3
3
  module Test
4
- class Browser
4
+ class UiSimulator
5
5
  TYPES = {
6
6
  inline: Js::InlineBus,
7
7
  nodo: Js::NodoBus,
8
+ mruby_wasm: Js::NodoBus,
8
9
  }
9
10
 
10
- attr_reader :system_json, :router, :type
11
- def initialize(system_json:, router:, type:)
11
+ attr_reader :system_json, :router, :type, :contract_path, :validation
12
+ def initialize(type:, bus:, router:, contract_path: nil, validation: :strict)
12
13
  unless TYPES.keys.include?(type)
13
14
  raise ArgumentError.new(
14
15
  "type: #{type.inspect} not allowed. Must be one of #{TYPES.keys}"
@@ -17,8 +18,10 @@ module Interfacets
17
18
 
18
19
  @type = type
19
20
  # Serialize and deserialize to ensure proper data structure for JS
20
- @system_json = JSON.parse(system_json.to_json)
21
+ @system_json = JSON.parse(bus.client_system_json(only_facets: type == :inline).to_json)
21
22
  @router = router
23
+ @contract_path = contract_path
24
+ @validation = validation
22
25
  end
23
26
 
24
27
  def visit(path)
@@ -43,14 +46,23 @@ module Interfacets
43
46
  c("dom")
44
47
  end
45
48
 
49
+ def timers
50
+ c("timer")
51
+ end
52
+
46
53
  private
47
54
 
48
55
  def js
49
56
  @js ||= (
57
+ validation_engine = if contract_path
58
+ ValidationEngine.new(ComponentRegistry.load(contract_path), validation_mode: validation)
59
+ end
60
+
50
61
  js_api = Test::Js::Receivers::Api.new(name: "interfacets:api", router:)
51
- js_react = Test::Js::Receivers::React.new(name: "dom")
62
+ js_react = Test::Js::Receivers::React.new(name: "dom", validation_engine:)
52
63
  js_url = Test::Js::Receivers::Url.new(name: "url")
53
- receivers = [js_api, js_react, js_url]
64
+ js_timer = Test::Js::Receivers::Timer.new(name: "timer")
65
+ receivers = [js_api, js_react, js_url, js_timer]
54
66
 
55
67
  TYPES.fetch(type).new(
56
68
  receiver_index: receivers.map { [_1.name, _1] }.to_h,