hokusai-zero 0.1.1

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 (166) hide show
  1. checksums.yaml +7 -0
  2. data/Dockerfile +26 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +91 -0
  5. data/LICENSE +21 -0
  6. data/README.md +28 -0
  7. data/ast/genheader +3 -0
  8. data/ast/include/hashmap.c +1151 -0
  9. data/ast/include/hashmap.c.license +20 -0
  10. data/ast/include/hashmap.h +54 -0
  11. data/ast/src/core/ast.c +448 -0
  12. data/ast/src/core/ast.h +259 -0
  13. data/ast/src/core/common.h +24 -0
  14. data/ast/src/core/component.c +85 -0
  15. data/ast/src/core/component.h +35 -0
  16. data/ast/src/core/hml.c +665 -0
  17. data/ast/src/core/hml.h +11 -0
  18. data/ast/src/core/input.c +458 -0
  19. data/ast/src/core/input.h +118 -0
  20. data/ast/src/core/style.c +101 -0
  21. data/ast/src/core/style.h +41 -0
  22. data/ast/src/core/text.c +784 -0
  23. data/ast/src/core/text.h +93 -0
  24. data/ast/src/core/util.c +140 -0
  25. data/ast/src/core/util.h +48 -0
  26. data/ast/src/hokusai.c +6 -0
  27. data/ast/src/hokusai.h +6 -0
  28. data/ast/test/fixtures/test.ui +13 -0
  29. data/ast/test/greatest.h +1266 -0
  30. data/ast/test/hokusai.c +14 -0
  31. data/ast/test/parser.c +234 -0
  32. data/ast/test/text.c +116 -0
  33. data/ext/extconf.rb +27 -0
  34. data/grammar/Cargo.lock +80 -0
  35. data/grammar/Cargo.toml +26 -0
  36. data/grammar/binding.gyp +20 -0
  37. data/grammar/bindings/node/binding.cc +28 -0
  38. data/grammar/bindings/node/index.js +19 -0
  39. data/grammar/bindings/rust/build.rs +40 -0
  40. data/grammar/bindings/rust/lib.rs +52 -0
  41. data/grammar/corpus/1_document.txt +131 -0
  42. data/grammar/corpus/2_selectors.txt +58 -0
  43. data/grammar/corpus/3_spaces.txt +69 -0
  44. data/grammar/corpus/4_errors.txt +10 -0
  45. data/grammar/corpus/5_macros.txt +175 -0
  46. data/grammar/corpus/6_styles.txt +81 -0
  47. data/grammar/grammar.js +275 -0
  48. data/grammar/package-lock.json +34 -0
  49. data/grammar/package.json +33 -0
  50. data/grammar/src/grammar.json +1269 -0
  51. data/grammar/src/node-types.json +474 -0
  52. data/grammar/src/parser.c +5772 -0
  53. data/grammar/src/scanner.c +258 -0
  54. data/grammar/src/tree_sitter/parser.h +230 -0
  55. data/grammar/src/tree_sitter/scanner.h +12 -0
  56. data/grammar/test.nml +10 -0
  57. data/hokusai.gemspec +19 -0
  58. data/ui/examples/assets/DigitalDisplay.ttf +0 -0
  59. data/ui/examples/assets/OpenSans-Regular.ttf +0 -0
  60. data/ui/examples/assets/addy.png +0 -0
  61. data/ui/examples/assets/baby_sean.png +0 -0
  62. data/ui/examples/assets/football-troll.png +0 -0
  63. data/ui/examples/assets/gear.png +0 -0
  64. data/ui/examples/assets/icecold.ttf +0 -0
  65. data/ui/examples/assets/science-troll.png +0 -0
  66. data/ui/examples/buddy.rb +31 -0
  67. data/ui/examples/clock.rb +58 -0
  68. data/ui/examples/counter.rb +123 -0
  69. data/ui/examples/dynamic.rb +147 -0
  70. data/ui/examples/foobar.rb +236 -0
  71. data/ui/examples/stock.rb +115 -0
  72. data/ui/examples/stock_decider/option.rb +74 -0
  73. data/ui/examples/tic_tac_toe.rb +246 -0
  74. data/ui/lib/lib_hokusai.rb +425 -0
  75. data/ui/spec/hokusai/ast_spec.rb +88 -0
  76. data/ui/spec/hokusai/automation/keys_transcoder_spec.rb +50 -0
  77. data/ui/spec/hokusai/automation/selector_spec.rb +68 -0
  78. data/ui/spec/hokusai/block_spec.rb +126 -0
  79. data/ui/spec/hokusai/directives_spec.rb +327 -0
  80. data/ui/spec/hokusai/e2e/client_spec.rb +58 -0
  81. data/ui/spec/hokusai/e2e/meta_spec.rb +42 -0
  82. data/ui/spec/hokusai/publisher_spec.rb +38 -0
  83. data/ui/spec/hokusai/slots_spec.rb +150 -0
  84. data/ui/spec/hokusai/util/piece_table_spec.rb +90 -0
  85. data/ui/spec/hokusai_spec.rb +0 -0
  86. data/ui/spec/spec_helper.rb +30 -0
  87. data/ui/src/hokusai/ast.rb +446 -0
  88. data/ui/src/hokusai/automation/client.rb +167 -0
  89. data/ui/src/hokusai/automation/constants.rb +98 -0
  90. data/ui/src/hokusai/automation/converters/selector_converter.rb +61 -0
  91. data/ui/src/hokusai/automation/driver.rb +54 -0
  92. data/ui/src/hokusai/automation/driver_command_queue.rb +50 -0
  93. data/ui/src/hokusai/automation/driver_commands/base.rb +79 -0
  94. data/ui/src/hokusai/automation/driver_commands/get_attribute.rb +41 -0
  95. data/ui/src/hokusai/automation/driver_commands/invoke.rb +33 -0
  96. data/ui/src/hokusai/automation/driver_commands/locate.rb +48 -0
  97. data/ui/src/hokusai/automation/driver_commands/trigger_keyboard.rb +94 -0
  98. data/ui/src/hokusai/automation/driver_commands/trigger_mouse.rb +213 -0
  99. data/ui/src/hokusai/automation/keys_transcoder.rb +128 -0
  100. data/ui/src/hokusai/automation/selector.rb +39 -0
  101. data/ui/src/hokusai/automation/server.rb +114 -0
  102. data/ui/src/hokusai/automation.rb +3 -0
  103. data/ui/src/hokusai/backends/raylib/config.rb +47 -0
  104. data/ui/src/hokusai/backends/raylib/font.rb +113 -0
  105. data/ui/src/hokusai/backends/raylib/keys.rb +124 -0
  106. data/ui/src/hokusai/backends/raylib.rb +449 -0
  107. data/ui/src/hokusai/backends/sdl2/Monaco.ttf +0 -0
  108. data/ui/src/hokusai/backends/sdl2/color.rb +12 -0
  109. data/ui/src/hokusai/backends/sdl2/config.rb +31 -0
  110. data/ui/src/hokusai/backends/sdl2/font.rb +127 -0
  111. data/ui/src/hokusai/backends/sdl2/keys.rb +119 -0
  112. data/ui/src/hokusai/backends/sdl2.rb +529 -0
  113. data/ui/src/hokusai/block.rb +237 -0
  114. data/ui/src/hokusai/blocks/button.rb +100 -0
  115. data/ui/src/hokusai/blocks/checkbox.rb +51 -0
  116. data/ui/src/hokusai/blocks/circle.rb +28 -0
  117. data/ui/src/hokusai/blocks/clipped.rb +23 -0
  118. data/ui/src/hokusai/blocks/cursor.rb +49 -0
  119. data/ui/src/hokusai/blocks/dynamic.rb +37 -0
  120. data/ui/src/hokusai/blocks/empty.rb +10 -0
  121. data/ui/src/hokusai/blocks/hblock.rb +35 -0
  122. data/ui/src/hokusai/blocks/image.rb +18 -0
  123. data/ui/src/hokusai/blocks/input.rb +200 -0
  124. data/ui/src/hokusai/blocks/label.rb +39 -0
  125. data/ui/src/hokusai/blocks/panel.rb +126 -0
  126. data/ui/src/hokusai/blocks/rect.rb +24 -0
  127. data/ui/src/hokusai/blocks/scissor_begin.rb +18 -0
  128. data/ui/src/hokusai/blocks/scissor_end.rb +12 -0
  129. data/ui/src/hokusai/blocks/scrollbar.rb +103 -0
  130. data/ui/src/hokusai/blocks/selectable.rb +77 -0
  131. data/ui/src/hokusai/blocks/svg.rb +20 -0
  132. data/ui/src/hokusai/blocks/text.rb +214 -0
  133. data/ui/src/hokusai/blocks/titlebar/osx.rb +145 -0
  134. data/ui/src/hokusai/blocks/toggle.rb +55 -0
  135. data/ui/src/hokusai/blocks/vblock.rb +35 -0
  136. data/ui/src/hokusai/commands/base.rb +22 -0
  137. data/ui/src/hokusai/commands/circle.rb +47 -0
  138. data/ui/src/hokusai/commands/image.rb +45 -0
  139. data/ui/src/hokusai/commands/rect.rb +158 -0
  140. data/ui/src/hokusai/commands/scissor.rb +22 -0
  141. data/ui/src/hokusai/commands/text.rb +92 -0
  142. data/ui/src/hokusai/commands.rb +87 -0
  143. data/ui/src/hokusai/diff.rb +124 -0
  144. data/ui/src/hokusai/error.rb +3 -0
  145. data/ui/src/hokusai/event.rb +54 -0
  146. data/ui/src/hokusai/events/keyboard.rb +84 -0
  147. data/ui/src/hokusai/events/mouse.rb +172 -0
  148. data/ui/src/hokusai/font.rb +280 -0
  149. data/ui/src/hokusai/meta.rb +152 -0
  150. data/ui/src/hokusai/mounting/loop_entry.rb +230 -0
  151. data/ui/src/hokusai/mounting/mount_entry.rb +74 -0
  152. data/ui/src/hokusai/mounting/update_entry.rb +101 -0
  153. data/ui/src/hokusai/node.rb +98 -0
  154. data/ui/src/hokusai/node_mounter.rb +102 -0
  155. data/ui/src/hokusai/painter.rb +214 -0
  156. data/ui/src/hokusai/publisher.rb +32 -0
  157. data/ui/src/hokusai/style.rb +72 -0
  158. data/ui/src/hokusai/types.rb +266 -0
  159. data/ui/src/hokusai/util/clamping_iterator.rb +202 -0
  160. data/ui/src/hokusai/util/piece_table.rb +111 -0
  161. data/ui/src/hokusai/util/selection.rb +145 -0
  162. data/ui/src/hokusai.rb +120 -0
  163. data/ui/vendor/.gitkeep +0 -0
  164. data/vendor/.gitkeep +0 -0
  165. data/xmake.lua +192 -0
  166. metadata +222 -0
@@ -0,0 +1,88 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe Hokusai::Ast do
4
+ let(:template) do
5
+ <<~EOF
6
+ [template]
7
+ first#id.class1.class2 { prop1="one" prop2="two" prop3="three" @event1="first" @event2="second" }
8
+ EOF
9
+ end
10
+
11
+ let(:parent) { Hokusai::Ast.parse(template, "root") }
12
+ let(:ast) { parent.children.first }
13
+
14
+ it ".parse returns a new ast from a template" do
15
+ expect(parent).to be_instance_of(Hokusai::Ast), "#{ast.class} not #{Hokusai::Ast}"
16
+ end
17
+
18
+ it "#type returns the ast type" do
19
+ expect(ast.type).to eq("first")
20
+ end
21
+
22
+ it "#id returns the ast id" do
23
+ expect(ast.id).to eq("id")
24
+ end
25
+
26
+ it "#each_event iterates events" do
27
+ count = 0
28
+ ast.each_event do |_|
29
+ count += 1
30
+ end
31
+
32
+ expect(count).to eq(2)
33
+ end
34
+
35
+ it "#each_prop iterates props" do
36
+ count = 0
37
+ ast.each_prop do |_|
38
+ count += 1
39
+ end
40
+
41
+ expect(count).to eq(3)
42
+ end
43
+
44
+ describe "#prop" do
45
+ let(:template) do
46
+ <<~EOF
47
+ [template]
48
+ first { :computed="true" }
49
+ second { notcomputed="true" }
50
+ EOF
51
+ end
52
+
53
+ it "returns nil if a prop is not found" do
54
+ expect(ast.prop("nope")).to be(nil)
55
+ end
56
+
57
+ it "returns a Hokusai::Ast::Prop" do
58
+ prop1 = parent.children[0].prop("computed")
59
+ prop2 = parent.children[1].prop("notcomputed")
60
+
61
+ expect(prop1).to be_a(Hokusai::Ast::Prop)
62
+ expect(prop1.value.method).to eq("true")
63
+ expect(prop1.computed?).to be(true)
64
+
65
+ expect(prop2.computed?).to be(false)
66
+ end
67
+ end
68
+
69
+ describe "#event" do
70
+ let(:template) do
71
+ <<~EOF
72
+ [template]
73
+ first { @click="handle_click", @hover="handle_hover" }
74
+ EOF
75
+ end
76
+
77
+ it "returns nil if an event isn't found" do
78
+ expect(ast.event("none")).to be(nil)
79
+ end
80
+
81
+ it "returns a Hokusai::Ast::Event" do
82
+ event = ast.event("hover")
83
+ expect(event).to be_a(Hokusai::Ast::Event)
84
+ expect(event.name).to eq("hover")
85
+ expect(event.value.method).to eq("handle_hover")
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,50 @@
1
+ describe Hokusai::Automation::KeysTranscoder do
2
+ def with_encoded(*keys)
3
+ encoded = Hokusai::Automation::KeysTranscoder.encode(keys.to_a)
4
+ yield encoded
5
+ end
6
+
7
+ def decode(encoded)
8
+ Hokusai::Automation::KeysTranscoder.decode(encoded)
9
+ end
10
+
11
+ it "decodes a simple string" do
12
+ with_encoded("h", "e", "l", "l", "o") do |encoded|
13
+ expect(decode(encoded)).to eq(%w[h e l l o])
14
+ end
15
+ end
16
+
17
+ it "decodes a string with modifiers" do
18
+ with_encoded([:shift, 'h'], 'e','l','l','o') do |encoded|
19
+ decode(encoded).should eq([[:lshift, 'h'], 'e','l','l','o'])
20
+ end
21
+ end
22
+
23
+ it "decodes multiple modifiers" do
24
+ with_encoded([:shift, 'h'], [:ctrl, 'e','l','l','o']) do |encoded|
25
+ decode(encoded).should eq([[:lshift, 'h'], [:lctrl, 'e','l','l','o']])
26
+ end
27
+ end
28
+
29
+ it "encodes a simple string" do
30
+ with_encoded('h','e','l','l','o') do |encoded|
31
+ encoded.should eq("hello")
32
+ end
33
+ end
34
+
35
+ it "encodeds a modifier key" do
36
+ with_encoded(:tab) do |encoded|
37
+ encoded.should eq("\ue004")
38
+ end
39
+ end
40
+
41
+ it "encodes mmultiple modifiers" do
42
+ with_encoded([:shift, 'h'], [:ctrl, 'e','l','l','o']) do |encoded|
43
+ encoded.should eq("\ue008h\ue000\ue009ello\ue000")
44
+ end
45
+ end
46
+
47
+ it "raises when a modifier cannot be found" do
48
+ expect { with_encoded('h','e','l','l','o', :foobar) {} }.to raise_error(Hokusai::Automation::Error)
49
+ end
50
+ end
@@ -0,0 +1,68 @@
1
+ describe Hokusai::Automation::Selector do
2
+ let(:selector_child_klass) do
3
+ Class.new(Hokusai::Block) do
4
+ template <<~EOF
5
+ [template]
6
+ virtual
7
+ EOF
8
+ end
9
+ end
10
+
11
+ let(:app) do
12
+ child = selector_child_klass
13
+
14
+ Class.new(Hokusai::Block) do
15
+ template <<~EOF
16
+ [template]
17
+ child#id.class1.class2
18
+ child2.class1
19
+ EOF
20
+
21
+ uses(child: child, child2: child)
22
+ end
23
+ end
24
+
25
+ let(:fixture) { app.mount }
26
+ let(:child1) { fixture.children[0] }
27
+ let(:child2) { fixture.children[1] }
28
+
29
+ let(:with_selector) do
30
+ ->(node: nil, id: nil, classes: nil, &block) do
31
+ block.call Hokusai::Automation::Selector.new(node, id, classes)
32
+ end
33
+ end
34
+
35
+ it "#match returns true if a selector matches a block" do
36
+ with_selector.call(id: "id") do |selector|
37
+ expect(selector.matches(child1)).to be(true)
38
+ end
39
+
40
+ with_selector.call(node: "child") do |selector|
41
+ expect(selector.matches(child1)).to be(true)
42
+ end
43
+
44
+ with_selector.call(classes: %w[class1 class2]) do |selector|
45
+ expect(selector.matches(child1)).to be(true)
46
+ end
47
+
48
+ with_selector.call(node: "child", id: "id", classes: %w[class1 class2]) do |selector|
49
+ expect(selector.matches(child1)).to be(true)
50
+ end
51
+
52
+ with_selector.call(node: "child2", id: nil, classes: %w[class1]) do |selector|
53
+ expect(selector.matches(child2)).to be(true)
54
+ end
55
+ end
56
+
57
+ it "#match returns false if a selector doesn't match a block" do
58
+ with_selector.call(id: "foobar") do |selector|
59
+ expect(selector.matches(child1)).to be(false)
60
+ end
61
+ end
62
+
63
+ it "#match returns false if any part of the selector doesn't match a block" do
64
+ with_selector.call(id: "id", node: "child", classes: %w[class1 class3]) do |selector|
65
+ expect(selector.matches(child1)).to be(false)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,126 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe Hokusai::Block do
4
+ let(:child_klass) do
5
+ Class.new(Hokusai::Block) do
6
+ template <<~EOF
7
+ [template]
8
+ virtual
9
+ EOF
10
+ computed! :echo
11
+
12
+ def self.foobar
13
+ nil
14
+ end
15
+
16
+ def hello
17
+ @state += 1
18
+
19
+ emit("get", @state)
20
+ end
21
+
22
+ def initialize(**args)
23
+ @state = 0
24
+
25
+ super(**args)
26
+ end
27
+ end
28
+ end
29
+
30
+ let(:parent) do
31
+ child_block = child_klass
32
+ klass = Class.new(Hokusai::Block) do
33
+ template <<~EOF
34
+ [template]
35
+ child { :echo="total" @get="get_it" }
36
+ EOF
37
+
38
+ attr_reader :state, :total, :mounted
39
+
40
+ uses(child: child_block)
41
+
42
+ def get_it(count)
43
+ @state += count
44
+ @total = "total: #{state}"
45
+ end
46
+
47
+ def on_mounted
48
+ @mounted = true
49
+ end
50
+
51
+ def initialize(**args)
52
+ @state = 0
53
+ @total = "total: #{state}"
54
+ @mounted = false
55
+
56
+ super(**args)
57
+ end
58
+ end
59
+
60
+ klass.mount
61
+ end
62
+
63
+ let(:child) do
64
+ parent.children.first
65
+ end
66
+
67
+ describe ".mount" do
68
+ it "mounts blocks together" do
69
+ expect(parent.children.size).to eq(1)
70
+ expect(child).to respond_to(:hello)
71
+ end
72
+
73
+ it "blocks have a node and portal node" do
74
+ expect(parent.node.type).to eq("root")
75
+ expect(child.node.portal.type).to eq("child")
76
+ end
77
+
78
+ it "mounts props into children" do
79
+ expect(child.echo).to eq("total: 0")
80
+ end
81
+ end
82
+
83
+ describe "#emit" do
84
+ it "emits events to the parent" do
85
+ child.emit("get", 1)
86
+
87
+ expect(parent.state).to eq(1)
88
+ end
89
+
90
+ it "raises when an emit is missing a param" do
91
+ expect { child.emit("get") }.to raise_error(ArgumentError)
92
+ end
93
+
94
+ it "does not raise when an emit is not subscribe to" do
95
+ expect { child.emit("foobarrr", 1,23) }.not_to raise_error
96
+ end
97
+ end
98
+
99
+ describe "#update" do
100
+ it "updates child props with new state" do
101
+ child.emit("get", 1)
102
+ expect(child.echo).to eq("total: 0")
103
+
104
+ parent.update
105
+ expect(child.echo).to eq("total: 1")
106
+ end
107
+ end
108
+
109
+ describe ".using" do
110
+ it "returns a Hokusai::Block by a key" do
111
+ expect(parent.class.use("child")).to respond_to(:foobar)
112
+ end
113
+
114
+ it "raise if the key isn't found" do
115
+ expect { parent.class.use("whaaa") }.to raise_error(Hokusai::Error) do |err|
116
+ expect(err.message).to match(/Type whaaa is not used/)
117
+ end
118
+ end
119
+ end
120
+
121
+ describe "#on_mounted" do
122
+ it "triggers when the block is mounted" do
123
+ expect(parent.mounted).to be(true)
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,327 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe "Block directives" do
4
+ let(:virtual) do
5
+ klass = Class.new(Hokusai::Block) do
6
+ template <<~EOF
7
+ [template]
8
+ virtual
9
+ EOF
10
+ end
11
+
12
+ klass
13
+ end
14
+
15
+ let(:slotted) do
16
+ klass = Class.new(Hokusai::Block) do
17
+ template <<~EOF
18
+ [template]
19
+ slot
20
+ EOF
21
+ end
22
+
23
+ klass
24
+ end
25
+
26
+ describe "if directive" do
27
+ let(:condition1) do
28
+ virtual_klass = virtual
29
+ klass = Class.new(Hokusai::Block) do
30
+ template <<~EOF
31
+ [template]
32
+ type { content="1" }
33
+ slot
34
+ EOF
35
+
36
+ uses(type: virtual_klass)
37
+ end
38
+
39
+ klass
40
+ end
41
+
42
+ let(:condition2) do
43
+ virtual_klass = virtual
44
+ klass = Class.new(Hokusai::Block) do
45
+ template <<~EOF
46
+ [template]
47
+ type { content="2" }
48
+ slot
49
+ EOF
50
+
51
+ uses(type: virtual_klass)
52
+ end
53
+
54
+ klass
55
+ end
56
+
57
+ let(:branch) do
58
+ virtual_klass = virtual
59
+ cond1 = condition1
60
+ cond2 = condition2
61
+ slot = slotted
62
+ klass = Class.new(Hokusai::Block) do
63
+ template <<~EOF
64
+ [template]
65
+ slotted
66
+ [if="state"]
67
+ cond1
68
+ control1
69
+ [else]
70
+ cond2
71
+ control2
72
+ control
73
+ EOF
74
+
75
+ uses(
76
+ slotted: slot,
77
+ cond1: cond1,
78
+ cond2: cond2,
79
+ control: virtual_klass,
80
+ control1: virtual_klass,
81
+ control2: virtual_klass
82
+ )
83
+
84
+ attr_accessor :state
85
+
86
+ def initialize(**props)
87
+ @state = true
88
+
89
+ super
90
+ end
91
+ end
92
+ end
93
+
94
+ let (:content) do
95
+ ->(root) do
96
+ root
97
+ .children[0]
98
+ .children[0]
99
+ .children[0]
100
+ .node
101
+ .meta
102
+ .get_prop(:content)
103
+ end
104
+ end
105
+
106
+ let (:portal_type) do
107
+ ->(root) do
108
+ root
109
+ .children[0]
110
+ .children[0]
111
+ .children[1]
112
+ .node
113
+ .type
114
+ end
115
+ end
116
+
117
+ it "simple condition" do
118
+ root = branch.mount
119
+ expect(content.call(root)).to eq("1")
120
+
121
+ root.state = false
122
+ root.update
123
+ expect(content.call(root)).to eq("2")
124
+ end
125
+
126
+ it "switches back and forth" do
127
+ root = branch.mount
128
+
129
+ expect(content.call(root)).to eq("1")
130
+ expect(root.children[0].children[-1].node.type).to eq("control")
131
+
132
+ root.state = false
133
+ root.update
134
+
135
+ expect(content.call(root)).to eq("2")
136
+ expect(root.children[0].children[-1].node.type).to eq("control")
137
+
138
+ root.state = true
139
+ root.update
140
+
141
+ expect(content.call(root)).to eq("1")
142
+ expect(root.children[0].children[-1].node.type).to eq("control")
143
+ end
144
+
145
+ it "mounts portaled children into the slot" do
146
+ root = branch.mount
147
+
148
+ expect(portal_type.call(root)).to eq("control1")
149
+
150
+ root.state = false
151
+ root.update
152
+
153
+ expect(portal_type.call(root)).to eq("control2")
154
+ end
155
+ end
156
+
157
+ describe "loop directives" do
158
+ let(:cell) do
159
+ virtual_klass = virtual
160
+ klass = Class.new(Hokusai::Block) do
161
+ template <<~EOF
162
+ [template]
163
+ empty { :content="value" }
164
+ EOF
165
+
166
+ computed! :value
167
+
168
+ uses(empty: virtual_klass)
169
+ end
170
+
171
+ klass
172
+ end
173
+
174
+ let(:column) do
175
+ cell_klass = cell
176
+ slotted_klass = slotted
177
+
178
+ klass = Class.new(Hokusai::Block) do
179
+ template <<~EOF
180
+ [template]
181
+ slotted
182
+ [for="value in columns"]
183
+ cell { :value="value" }
184
+ EOF
185
+ computed! :row_index
186
+
187
+ uses(cell: cell_klass, slotted: slotted_klass)
188
+
189
+ def columns
190
+ %w[A B C D].map { |letter| "#{letter}#{row_index}" }
191
+ end
192
+ end
193
+
194
+ klass
195
+ end
196
+
197
+ let(:csv) do
198
+ column_klass = column
199
+ slotted_klass = slotted
200
+
201
+
202
+ klass = Class.new(Hokusai::Block) do
203
+ template <<~EOF
204
+ [template]
205
+ slotted
206
+ [for="row in rows"]
207
+ column { :row_index="row" }
208
+ EOF
209
+
210
+ uses(slotted: slotted_klass, column: column_klass)
211
+
212
+ def rows
213
+ %w[1 2 3 4]
214
+ end
215
+ end
216
+
217
+ klass
218
+ end
219
+
220
+ let(:list_item) do
221
+ virtual_klass = virtual
222
+
223
+ klass = Class.new(Hokusai::Block) do
224
+ template <<~EOF
225
+ [template]
226
+ empty
227
+ EOF
228
+
229
+ attr_accessor :state
230
+
231
+ uses(empty: virtual_klass)
232
+
233
+ computed! :item
234
+
235
+ def initialize(**args)
236
+ @state = "hello"
237
+
238
+ super
239
+ end
240
+ end
241
+
242
+ klass
243
+ end
244
+
245
+ let(:list) do
246
+ list_item_klass = list_item
247
+ slotted_klass = slotted
248
+
249
+ klass = Class.new(Hokusai::Block) do
250
+ template <<~EOF
251
+ [template]
252
+ slotted
253
+ [for="item in list"]
254
+ list_item { :item="item" :key="key(item, index)" }
255
+ EOF
256
+
257
+ attr_accessor :limit
258
+
259
+ uses(slotted: slotted_klass, list_item: list_item_klass)
260
+
261
+ def list
262
+ (0...limit).to_a
263
+ end
264
+
265
+ def key(item, index)
266
+ "#{item}-#{index}"
267
+ end
268
+
269
+ def initialize(**args)
270
+ @limit = 5
271
+
272
+ super
273
+ end
274
+ end
275
+
276
+ klass
277
+ end
278
+
279
+ it "mounts nested loops" do
280
+ root = csv.mount
281
+
282
+ rows = %w[1 2 3 4]
283
+ cols = %w[A B C D]
284
+
285
+ rows.each_with_index do |row, ridx|
286
+ actual_rows = root.children[0].children
287
+ expect(actual_rows.size).to eq(4)
288
+
289
+ actual_row = actual_rows[ridx]
290
+ expect(actual_row.row_index).to eq(row)
291
+
292
+ actual_columns = actual_row.children[0].children
293
+ expect(actual_columns.size).to eq(4)
294
+
295
+ cols.each_with_index do |col, cidx|
296
+ expect(actual_columns[cidx].value).to eq("#{col}#{row}")
297
+ end
298
+ end
299
+ end
300
+
301
+ it "diffs so that unchanged blocks are not remounted" do
302
+ root = list.mount
303
+ children = root.children[0].children
304
+ expect(children.size).to eq(5)
305
+
306
+ children[0].state = "1"
307
+ children[1].state = "2"
308
+ children[2].state = "3"
309
+ children[3].state = "4"
310
+ children[4].state = "5"
311
+
312
+ root.limit = 2
313
+ root.update
314
+ expect(root.children[0].children.size).to eq(2)
315
+
316
+
317
+ root.limit = 3
318
+ root.update
319
+ expect(root.children[0].children.size).to eq(3)
320
+
321
+ updated_children = root.children[0].children
322
+ expect(updated_children[0].state).to eq("1")
323
+ expect(updated_children[1].state).to eq("2")
324
+ expect(updated_children[2].state).to eq("hello")
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,58 @@
1
+ describe Hokusai::Automation::Client do
2
+ let(:counter) do
3
+ klass = Class.new(Hokusai::Block) do
4
+ template <<~EOF
5
+ [template]
6
+ vblock
7
+ label#count { :content="count" size="90" }
8
+ hblock.counter
9
+ label#inc { @click="increment" content="Increment" }
10
+ label#dec { @click="decrement" content="Decrement" }
11
+ EOF
12
+
13
+ uses(
14
+ vblock: Hokusai::Blocks::Vblock,
15
+ hblock: Hokusai::Blocks::Hblock,
16
+ label: Hokusai::Blocks::Label
17
+ )
18
+
19
+ attr_accessor :count
20
+
21
+ def increment(_event)
22
+ self.count += 1
23
+ end
24
+
25
+ def decrement(_event)
26
+ self.count -= 1
27
+ end
28
+
29
+ def initialize(**args)
30
+ @count = 0
31
+
32
+ super(**args)
33
+ end
34
+ end
35
+
36
+ klass
37
+ end
38
+
39
+ it "client works" do
40
+ with_app(counter) do |client, app|
41
+ output = client.block("#count")
42
+
43
+ expect(output.prop("content")).to eq(0)
44
+
45
+ client.block(".counter #inc") do |increment|
46
+ 3.times { increment.click }
47
+ end
48
+
49
+ expect(output.prop("content")).to eq(3)
50
+
51
+ client.block("#dec") do |decrement|
52
+ 2.times { decrement.click }
53
+ end
54
+
55
+ expect(output.prop("content")).to eq(1)
56
+ end
57
+ end
58
+ end