ruby_wasm_ui 0.8.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.
- checksums.yaml +7 -0
- data/.cursor/rules/ruby_comments.mdc +29 -0
- data/.github/workflows/playwright.yml +74 -0
- data/.github/workflows/rspec.yml +33 -0
- data/.node-version +1 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +218 -0
- data/Rakefile +4 -0
- data/docs/conditional-rendering.md +119 -0
- data/docs/lifecycle-hooks.md +75 -0
- data/docs/list-rendering.md +51 -0
- data/examples/Gemfile +5 -0
- data/examples/Gemfile.lock +41 -0
- data/examples/Makefile +15 -0
- data/examples/npm-packages/runtime/counter/index.html +28 -0
- data/examples/npm-packages/runtime/counter/index.rb +62 -0
- data/examples/npm-packages/runtime/counter/index.spec.js +42 -0
- data/examples/npm-packages/runtime/hello/index.html +28 -0
- data/examples/npm-packages/runtime/hello/index.rb +29 -0
- data/examples/npm-packages/runtime/hello/index.spec.js +53 -0
- data/examples/npm-packages/runtime/input/index.html +28 -0
- data/examples/npm-packages/runtime/input/index.rb +46 -0
- data/examples/npm-packages/runtime/input/index.spec.js +58 -0
- data/examples/npm-packages/runtime/list/index.html +27 -0
- data/examples/npm-packages/runtime/list/index.rb +33 -0
- data/examples/npm-packages/runtime/list/index.spec.js +46 -0
- data/examples/npm-packages/runtime/on_mounted_demo/index.html +40 -0
- data/examples/npm-packages/runtime/on_mounted_demo/index.rb +59 -0
- data/examples/npm-packages/runtime/on_mounted_demo/index.spec.js +50 -0
- data/examples/npm-packages/runtime/r_if_attribute_demo/index.html +34 -0
- data/examples/npm-packages/runtime/r_if_attribute_demo/index.rb +113 -0
- data/examples/npm-packages/runtime/r_if_attribute_demo/index.spec.js +140 -0
- data/examples/npm-packages/runtime/random_cocktail/index.html +27 -0
- data/examples/npm-packages/runtime/random_cocktail/index.rb +69 -0
- data/examples/npm-packages/runtime/random_cocktail/index.spec.js +101 -0
- data/examples/npm-packages/runtime/search_field/index.html +27 -0
- data/examples/npm-packages/runtime/search_field/index.rb +39 -0
- data/examples/npm-packages/runtime/search_field/index.spec.js +59 -0
- data/examples/npm-packages/runtime/todos/index.html +28 -0
- data/examples/npm-packages/runtime/todos/index.rb +239 -0
- data/examples/npm-packages/runtime/todos/index.spec.js +161 -0
- data/examples/npm-packages/runtime/todos/todos_repository.rb +23 -0
- data/examples/package.json +12 -0
- data/examples/src/counter/index.html +23 -0
- data/examples/src/counter/index.rb +60 -0
- data/lib/ruby_wasm_ui +1 -0
- data/lib/ruby_wasm_ui.rb +1 -0
- data/package-lock.json +100 -0
- data/package.json +32 -0
- data/packages/npm-packages/runtime/Gemfile +3 -0
- data/packages/npm-packages/runtime/Gemfile.lock +26 -0
- data/packages/npm-packages/runtime/README.md +5 -0
- data/packages/npm-packages/runtime/eslint.config.mjs +16 -0
- data/packages/npm-packages/runtime/package-lock.json +6668 -0
- data/packages/npm-packages/runtime/package.json +38 -0
- data/packages/npm-packages/runtime/rollup.config.mjs +89 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/component_spec.rb +416 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/dom/scheduler_spec.rb +98 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/nodes_equal_spec.rb +190 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/template/build_conditional_group_spec.rb +505 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/template/build_for_group_spec.rb +377 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/template/build_vdom_spec.rb +573 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/template/parser_spec.rb +627 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/utils/arrays_spec.rb +228 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/utils/objects_spec.rb +127 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/utils/props_spec.rb +205 -0
- data/packages/npm-packages/runtime/spec/ruby_wasm_ui/utils/strings_spec.rb +107 -0
- data/packages/npm-packages/runtime/spec/spec_helper.rb +16 -0
- data/packages/npm-packages/runtime/src/__tests__/sample.test.js +5 -0
- data/packages/npm-packages/runtime/src/index.js +37 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/app.rb +53 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/component.rb +215 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dispatcher.rb +46 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dom/attributes.rb +105 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dom/destroy_dom.rb +63 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dom/events.rb +40 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dom/mount_dom.rb +108 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dom/patch_dom.rb +237 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dom/scheduler.rb +51 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/dom.rb +13 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/nodes_equal.rb +45 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/template/build_conditional_group.rb +150 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/template/build_for_group.rb +125 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/template/build_vdom.rb +220 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/template/parser.rb +134 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/template.rb +11 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/utils/arrays.rb +185 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/utils/objects.rb +37 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/utils/props.rb +25 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/utils/strings.rb +19 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/utils.rb +11 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/vdom.rb +84 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui/version.rb +5 -0
- data/packages/npm-packages/runtime/src/ruby_wasm_ui.rb +14 -0
- data/packages/npm-packages/runtime/vitest.config.js +8 -0
- data/playwright.config.js +78 -0
- data/sig/ruby_wasm_ui.rbs +4 -0
- metadata +168 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubyWasmUi::NodesEqual do
|
|
6
|
+
describe '.equal?' do
|
|
7
|
+
context 'Nodes with different types' do
|
|
8
|
+
it 'returns false for nodes with different types' do
|
|
9
|
+
text_node = RubyWasmUi::Vdom.h_string('Hello')
|
|
10
|
+
element_node = RubyWasmUi::Vdom.h('div')
|
|
11
|
+
|
|
12
|
+
result = described_class.equal?(text_node, element_node)
|
|
13
|
+
expect(result).to be false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'returns false for TEXT and ELEMENT type nodes' do
|
|
17
|
+
text_node = RubyWasmUi::Vdom.h_string('Hello')
|
|
18
|
+
element_node = RubyWasmUi::Vdom.h('span')
|
|
19
|
+
|
|
20
|
+
result = described_class.equal?(text_node, element_node)
|
|
21
|
+
expect(result).to be false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'returns false for ELEMENT and FRAGMENT type nodes' do
|
|
25
|
+
element_node = RubyWasmUi::Vdom.h('div')
|
|
26
|
+
fragment_node = RubyWasmUi::Vdom.h_fragment([])
|
|
27
|
+
|
|
28
|
+
result = described_class.equal?(element_node, fragment_node)
|
|
29
|
+
expect(result).to be false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'ELEMENT type nodes' do
|
|
34
|
+
it 'returns true for ELEMENT nodes with the same tag name' do
|
|
35
|
+
node_one = RubyWasmUi::Vdom.h('div')
|
|
36
|
+
node_two = RubyWasmUi::Vdom.h('div')
|
|
37
|
+
|
|
38
|
+
result = described_class.equal?(node_one, node_two)
|
|
39
|
+
expect(result).to be true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'returns false for ELEMENT nodes with different tag names' do
|
|
43
|
+
node_one = RubyWasmUi::Vdom.h('div')
|
|
44
|
+
node_two = RubyWasmUi::Vdom.h('span')
|
|
45
|
+
|
|
46
|
+
result = described_class.equal?(node_one, node_two)
|
|
47
|
+
expect(result).to be false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'returns false for ELEMENT nodes with different cases in tag names' do
|
|
51
|
+
node_one = RubyWasmUi::Vdom.h('DIV')
|
|
52
|
+
node_two = RubyWasmUi::Vdom.h('div')
|
|
53
|
+
|
|
54
|
+
result = described_class.equal?(node_one, node_two)
|
|
55
|
+
expect(result).to be false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'returns true for ELEMENT nodes with complex tag names' do
|
|
59
|
+
node_one = RubyWasmUi::Vdom.h('custom-element')
|
|
60
|
+
node_two = RubyWasmUi::Vdom.h('custom-element')
|
|
61
|
+
|
|
62
|
+
result = described_class.equal?(node_one, node_two)
|
|
63
|
+
expect(result).to be true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'returns true for ELEMENT nodes with the same tag name and same key' do
|
|
67
|
+
node_one = RubyWasmUi::Vdom.h('div', { key: 'test-key' })
|
|
68
|
+
node_two = RubyWasmUi::Vdom.h('div', { key: 'test-key' })
|
|
69
|
+
|
|
70
|
+
result = described_class.equal?(node_one, node_two)
|
|
71
|
+
expect(result).to be true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'returns false for ELEMENT nodes with the same tag name but different keys' do
|
|
75
|
+
node_one = RubyWasmUi::Vdom.h('div', { key: 'key1' })
|
|
76
|
+
node_two = RubyWasmUi::Vdom.h('div', { key: 'key2' })
|
|
77
|
+
|
|
78
|
+
result = described_class.equal?(node_one, node_two)
|
|
79
|
+
expect(result).to be false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'returns false for ELEMENT nodes with one having key and other having no key' do
|
|
83
|
+
node_one = RubyWasmUi::Vdom.h('div', { key: 'test-key' })
|
|
84
|
+
node_two = RubyWasmUi::Vdom.h('div')
|
|
85
|
+
|
|
86
|
+
result = described_class.equal?(node_one, node_two)
|
|
87
|
+
expect(result).to be false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns true for ELEMENT nodes with both having nil keys' do
|
|
91
|
+
node_one = RubyWasmUi::Vdom.h('div', { key: nil })
|
|
92
|
+
node_two = RubyWasmUi::Vdom.h('div', { key: nil })
|
|
93
|
+
|
|
94
|
+
result = described_class.equal?(node_one, node_two)
|
|
95
|
+
expect(result).to be true
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context 'COMPONENT type nodes' do
|
|
100
|
+
let(:dummy_component) { Class.new }
|
|
101
|
+
let(:another_component) { Class.new }
|
|
102
|
+
|
|
103
|
+
it 'returns true for COMPONENT nodes with the same component class' do
|
|
104
|
+
node_one = RubyWasmUi::Vdom.h(dummy_component)
|
|
105
|
+
node_two = RubyWasmUi::Vdom.h(dummy_component)
|
|
106
|
+
|
|
107
|
+
result = described_class.equal?(node_one, node_two)
|
|
108
|
+
expect(result).to be true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'returns false for COMPONENT nodes with different component classes' do
|
|
112
|
+
node_one = RubyWasmUi::Vdom.h(dummy_component)
|
|
113
|
+
node_two = RubyWasmUi::Vdom.h(another_component)
|
|
114
|
+
|
|
115
|
+
result = described_class.equal?(node_one, node_two)
|
|
116
|
+
expect(result).to be false
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'returns true for COMPONENT nodes with the same component class and same key' do
|
|
120
|
+
node_one = RubyWasmUi::Vdom.h(dummy_component, { key: 'test-key' })
|
|
121
|
+
node_two = RubyWasmUi::Vdom.h(dummy_component, { key: 'test-key' })
|
|
122
|
+
|
|
123
|
+
result = described_class.equal?(node_one, node_two)
|
|
124
|
+
expect(result).to be true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'returns false for COMPONENT nodes with the same component class but different keys' do
|
|
128
|
+
node_one = RubyWasmUi::Vdom.h(dummy_component, { key: 'key1' })
|
|
129
|
+
node_two = RubyWasmUi::Vdom.h(dummy_component, { key: 'key2' })
|
|
130
|
+
|
|
131
|
+
result = described_class.equal?(node_one, node_two)
|
|
132
|
+
expect(result).to be false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'returns false for COMPONENT nodes with one having key and other having no key' do
|
|
136
|
+
node_one = RubyWasmUi::Vdom.h(dummy_component, { key: 'test-key' })
|
|
137
|
+
node_two = RubyWasmUi::Vdom.h(dummy_component)
|
|
138
|
+
|
|
139
|
+
result = described_class.equal?(node_one, node_two)
|
|
140
|
+
expect(result).to be false
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
context 'TEXT type nodes' do
|
|
145
|
+
it 'returns true for TEXT nodes with the same value' do
|
|
146
|
+
node_one = RubyWasmUi::Vdom.h_string('Hello')
|
|
147
|
+
node_two = RubyWasmUi::Vdom.h_string('Hello')
|
|
148
|
+
|
|
149
|
+
result = described_class.equal?(node_one, node_two)
|
|
150
|
+
expect(result).to be true
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'returns false for TEXT nodes with different values' do
|
|
154
|
+
node_one = RubyWasmUi::Vdom.h_string('Hello')
|
|
155
|
+
node_two = RubyWasmUi::Vdom.h_string('World')
|
|
156
|
+
|
|
157
|
+
result = described_class.equal?(node_one, node_two)
|
|
158
|
+
expect(result).to be false
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'FRAGMENT type nodes' do
|
|
163
|
+
it 'returns true for FRAGMENT nodes' do
|
|
164
|
+
node_one = RubyWasmUi::Vdom.h_fragment([])
|
|
165
|
+
node_two = RubyWasmUi::Vdom.h_fragment([])
|
|
166
|
+
|
|
167
|
+
result = described_class.equal?(node_one, node_two)
|
|
168
|
+
expect(result).to be true
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context 'Edge cases' do
|
|
173
|
+
it 'returns true for ELEMENT nodes with empty string tag names' do
|
|
174
|
+
node_one = RubyWasmUi::Vdom.h('')
|
|
175
|
+
node_two = RubyWasmUi::Vdom.h('')
|
|
176
|
+
|
|
177
|
+
result = described_class.equal?(node_one, node_two)
|
|
178
|
+
expect(result).to be true
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'returns false for ELEMENT nodes with empty and non-empty tag names' do
|
|
182
|
+
node_one = RubyWasmUi::Vdom.h('')
|
|
183
|
+
node_two = RubyWasmUi::Vdom.h('div')
|
|
184
|
+
|
|
185
|
+
result = described_class.equal?(node_one, node_two)
|
|
186
|
+
expect(result).to be false
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubyWasmUi::Template::BuildConditionalGroup do
|
|
6
|
+
let(:mock_node_constants) { double('Node') }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
# Mock JS.global[:Node] constants
|
|
10
|
+
js_mock = double('JS')
|
|
11
|
+
allow(js_mock).to receive(:global).and_return({ Node: mock_node_constants })
|
|
12
|
+
stub_const('JS', js_mock)
|
|
13
|
+
|
|
14
|
+
allow(mock_node_constants).to receive(:[]).with(:ELEMENT_NODE).and_return(1)
|
|
15
|
+
allow(mock_node_constants).to receive(:[]).with(:TEXT_NODE).and_return(3)
|
|
16
|
+
end
|
|
17
|
+
describe '.has_conditional_attribute?' do
|
|
18
|
+
let(:mock_element) { double('element') }
|
|
19
|
+
let(:mock_attributes) { double('attributes') }
|
|
20
|
+
|
|
21
|
+
context 'when element has r-if attribute' do
|
|
22
|
+
before do
|
|
23
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(mock_attributes)
|
|
24
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(1)
|
|
25
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute)
|
|
26
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-if')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
let(:mock_attribute) { double('attribute') }
|
|
30
|
+
|
|
31
|
+
it 'returns true' do
|
|
32
|
+
result = described_class.has_conditional_attribute?(mock_element)
|
|
33
|
+
expect(result).to be true
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'when element has r-elsif attribute' do
|
|
38
|
+
before do
|
|
39
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(mock_attributes)
|
|
40
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(1)
|
|
41
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute)
|
|
42
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-elsif')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
let(:mock_attribute) { double('attribute') }
|
|
46
|
+
|
|
47
|
+
it 'returns true' do
|
|
48
|
+
result = described_class.has_conditional_attribute?(mock_element)
|
|
49
|
+
expect(result).to be true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'when element has r-else attribute' do
|
|
54
|
+
before do
|
|
55
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(mock_attributes)
|
|
56
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(1)
|
|
57
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute)
|
|
58
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-else')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
let(:mock_attribute) { double('attribute') }
|
|
62
|
+
|
|
63
|
+
it 'returns true' do
|
|
64
|
+
result = described_class.has_conditional_attribute?(mock_element)
|
|
65
|
+
expect(result).to be true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when element has no conditional attributes' do
|
|
70
|
+
before do
|
|
71
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(mock_attributes)
|
|
72
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(1)
|
|
73
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute)
|
|
74
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('class')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
let(:mock_attribute) { double('attribute') }
|
|
78
|
+
|
|
79
|
+
it 'returns false' do
|
|
80
|
+
result = described_class.has_conditional_attribute?(mock_element)
|
|
81
|
+
expect(result).to be false
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context 'when element has no attributes' do
|
|
86
|
+
before do
|
|
87
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(nil)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns false' do
|
|
91
|
+
result = described_class.has_conditional_attribute?(mock_element)
|
|
92
|
+
expect(result).to be false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe '.build_conditional_group' do
|
|
98
|
+
let(:mock_elements) { double('elements') }
|
|
99
|
+
let(:mock_element_if) { double('element_if') }
|
|
100
|
+
let(:mock_element_elsif) { double('element_elsif') }
|
|
101
|
+
let(:mock_element_else) { double('element_else') }
|
|
102
|
+
let(:mock_attributes_if) { double('attributes_if') }
|
|
103
|
+
let(:mock_attributes_elsif) { double('attributes_elsif') }
|
|
104
|
+
let(:mock_attributes_else) { double('attributes_else') }
|
|
105
|
+
let(:mock_attribute_if) { double('attribute_if') }
|
|
106
|
+
let(:mock_attribute_elsif) { double('attribute_elsif') }
|
|
107
|
+
let(:mock_attribute_else) { double('attribute_else') }
|
|
108
|
+
|
|
109
|
+
context 'with complete conditional chain (r-if, r-elsif, r-else)' do
|
|
110
|
+
before do
|
|
111
|
+
# Mock elements array
|
|
112
|
+
allow(mock_elements).to receive(:[]).with(:length).and_return(3)
|
|
113
|
+
allow(mock_elements).to receive(:[]).with(0).and_return(mock_element_if)
|
|
114
|
+
allow(mock_elements).to receive(:[]).with(1).and_return(mock_element_elsif)
|
|
115
|
+
allow(mock_elements).to receive(:[]).with(2).and_return(mock_element_else)
|
|
116
|
+
|
|
117
|
+
# Mock element types
|
|
118
|
+
allow(mock_element_if).to receive(:[]).with(:nodeType).and_return(1)
|
|
119
|
+
allow(mock_element_elsif).to receive(:[]).with(:nodeType).and_return(1)
|
|
120
|
+
allow(mock_element_else).to receive(:[]).with(:nodeType).and_return(1)
|
|
121
|
+
|
|
122
|
+
# Mock has_conditional_attribute? responses
|
|
123
|
+
allow(described_class).to receive(:has_conditional_attribute?)
|
|
124
|
+
.with(mock_element_if).and_return(true)
|
|
125
|
+
allow(described_class).to receive(:has_conditional_attribute?)
|
|
126
|
+
.with(mock_element_elsif).and_return(true)
|
|
127
|
+
allow(described_class).to receive(:has_conditional_attribute?)
|
|
128
|
+
.with(mock_element_else).and_return(true)
|
|
129
|
+
|
|
130
|
+
# Mock get_conditional_type responses
|
|
131
|
+
allow(described_class).to receive(:get_conditional_type)
|
|
132
|
+
.with(mock_element_if).and_return('r-if')
|
|
133
|
+
allow(described_class).to receive(:get_conditional_type)
|
|
134
|
+
.with(mock_element_elsif).and_return('r-elsif')
|
|
135
|
+
allow(described_class).to receive(:get_conditional_type)
|
|
136
|
+
.with(mock_element_else).and_return('r-else')
|
|
137
|
+
|
|
138
|
+
# Mock get_conditional_expression responses
|
|
139
|
+
allow(described_class).to receive(:get_conditional_expression)
|
|
140
|
+
.with(mock_element_if).and_return('condition1')
|
|
141
|
+
allow(described_class).to receive(:get_conditional_expression)
|
|
142
|
+
.with(mock_element_elsif).and_return('condition2')
|
|
143
|
+
allow(described_class).to receive(:get_conditional_expression)
|
|
144
|
+
.with(mock_element_else).and_return('true')
|
|
145
|
+
|
|
146
|
+
# Mock build_single_conditional_content responses
|
|
147
|
+
allow(described_class).to receive(:build_single_conditional_content)
|
|
148
|
+
.with(mock_element_if).and_return('content_if')
|
|
149
|
+
allow(described_class).to receive(:build_single_conditional_content)
|
|
150
|
+
.with(mock_element_elsif).and_return('content_elsif')
|
|
151
|
+
allow(described_class).to receive(:build_single_conditional_content)
|
|
152
|
+
.with(mock_element_else).and_return('content_else')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'builds complete conditional chain' do
|
|
156
|
+
result, next_index = described_class.build_conditional_group(mock_elements, 0)
|
|
157
|
+
|
|
158
|
+
expected = [
|
|
159
|
+
'if condition1',
|
|
160
|
+
' content_if',
|
|
161
|
+
'elsif condition2',
|
|
162
|
+
' content_elsif',
|
|
163
|
+
'else',
|
|
164
|
+
' content_else',
|
|
165
|
+
'end'
|
|
166
|
+
].join("\n")
|
|
167
|
+
|
|
168
|
+
expect(result).to eq(expected)
|
|
169
|
+
expect(next_index).to eq(3)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context 'with only r-if (no r-else)' do
|
|
174
|
+
before do
|
|
175
|
+
# Mock elements array
|
|
176
|
+
allow(mock_elements).to receive(:[]).with(:length).and_return(1)
|
|
177
|
+
allow(mock_elements).to receive(:[]).with(0).and_return(mock_element_if)
|
|
178
|
+
|
|
179
|
+
# Mock element type
|
|
180
|
+
allow(mock_element_if).to receive(:[]).with(:nodeType).and_return(1)
|
|
181
|
+
|
|
182
|
+
# Mock has_conditional_attribute? response
|
|
183
|
+
allow(described_class).to receive(:has_conditional_attribute?)
|
|
184
|
+
.with(mock_element_if).and_return(true)
|
|
185
|
+
|
|
186
|
+
# Mock get_conditional_type response
|
|
187
|
+
allow(described_class).to receive(:get_conditional_type)
|
|
188
|
+
.with(mock_element_if).and_return('r-if')
|
|
189
|
+
|
|
190
|
+
# Mock get_conditional_expression response
|
|
191
|
+
allow(described_class).to receive(:get_conditional_expression)
|
|
192
|
+
.with(mock_element_if).and_return('condition')
|
|
193
|
+
|
|
194
|
+
# Mock build_single_conditional_content response
|
|
195
|
+
allow(described_class).to receive(:build_single_conditional_content)
|
|
196
|
+
.with(mock_element_if).and_return('content')
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'builds conditional with default else clause' do
|
|
200
|
+
result, next_index = described_class.build_conditional_group(mock_elements, 0)
|
|
201
|
+
|
|
202
|
+
expected = [
|
|
203
|
+
'if condition',
|
|
204
|
+
' content',
|
|
205
|
+
'else',
|
|
206
|
+
' RubyWasmUi::Vdom.h_fragment([])',
|
|
207
|
+
'end'
|
|
208
|
+
].join("\n")
|
|
209
|
+
|
|
210
|
+
expect(result).to eq(expected)
|
|
211
|
+
expect(next_index).to eq(1)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
context 'with text nodes between elements' do
|
|
216
|
+
let(:mock_text_node) { double('text_node') }
|
|
217
|
+
|
|
218
|
+
before do
|
|
219
|
+
# Mock elements array with text node
|
|
220
|
+
allow(mock_elements).to receive(:[]).with(:length).and_return(3)
|
|
221
|
+
allow(mock_elements).to receive(:[]).with(0).and_return(mock_element_if)
|
|
222
|
+
allow(mock_elements).to receive(:[]).with(1).and_return(mock_text_node)
|
|
223
|
+
allow(mock_elements).to receive(:[]).with(2).and_return(mock_element_else)
|
|
224
|
+
|
|
225
|
+
# Mock node types
|
|
226
|
+
allow(mock_element_if).to receive(:[]).with(:nodeType).and_return(1)
|
|
227
|
+
allow(mock_text_node).to receive(:[]).with(:nodeType).and_return(3)
|
|
228
|
+
allow(mock_element_else).to receive(:[]).with(:nodeType).and_return(1)
|
|
229
|
+
|
|
230
|
+
# Mock has_conditional_attribute? responses
|
|
231
|
+
allow(described_class).to receive(:has_conditional_attribute?)
|
|
232
|
+
.with(mock_element_if).and_return(true)
|
|
233
|
+
allow(described_class).to receive(:has_conditional_attribute?)
|
|
234
|
+
.with(mock_element_else).and_return(true)
|
|
235
|
+
|
|
236
|
+
# Mock get_conditional_type responses
|
|
237
|
+
allow(described_class).to receive(:get_conditional_type)
|
|
238
|
+
.with(mock_element_if).and_return('r-if')
|
|
239
|
+
allow(described_class).to receive(:get_conditional_type)
|
|
240
|
+
.with(mock_element_else).and_return('r-else')
|
|
241
|
+
|
|
242
|
+
# Mock get_conditional_expression response
|
|
243
|
+
allow(described_class).to receive(:get_conditional_expression)
|
|
244
|
+
.with(mock_element_if).and_return('condition')
|
|
245
|
+
allow(described_class).to receive(:get_conditional_expression)
|
|
246
|
+
.with(mock_element_else).and_return('true')
|
|
247
|
+
|
|
248
|
+
# Mock build_single_conditional_content responses
|
|
249
|
+
allow(described_class).to receive(:build_single_conditional_content)
|
|
250
|
+
.with(mock_element_if).and_return('content_if')
|
|
251
|
+
allow(described_class).to receive(:build_single_conditional_content)
|
|
252
|
+
.with(mock_element_else).and_return('content_else')
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'skips text nodes and processes conditional elements' do
|
|
256
|
+
result, next_index = described_class.build_conditional_group(mock_elements, 0)
|
|
257
|
+
|
|
258
|
+
expected = [
|
|
259
|
+
'if condition',
|
|
260
|
+
' content_if',
|
|
261
|
+
'else',
|
|
262
|
+
' content_else',
|
|
263
|
+
'end'
|
|
264
|
+
].join("\n")
|
|
265
|
+
|
|
266
|
+
expect(result).to eq(expected)
|
|
267
|
+
expect(next_index).to eq(3)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
describe '.get_conditional_type' do
|
|
273
|
+
let(:mock_element) { double('element') }
|
|
274
|
+
let(:mock_attributes) { double('attributes') }
|
|
275
|
+
let(:mock_attribute) { double('attribute') }
|
|
276
|
+
|
|
277
|
+
before do
|
|
278
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(mock_attributes)
|
|
279
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(1)
|
|
280
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
context 'when element has r-if attribute' do
|
|
284
|
+
before do
|
|
285
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-if')
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it 'returns r-if' do
|
|
289
|
+
result = described_class.get_conditional_type(mock_element)
|
|
290
|
+
expect(result).to eq('r-if')
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
context 'when element has r-elsif attribute' do
|
|
295
|
+
before do
|
|
296
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-elsif')
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it 'returns r-elsif' do
|
|
300
|
+
result = described_class.get_conditional_type(mock_element)
|
|
301
|
+
expect(result).to eq('r-elsif')
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
context 'when element has r-else attribute' do
|
|
306
|
+
before do
|
|
307
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-else')
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'returns r-else' do
|
|
311
|
+
result = described_class.get_conditional_type(mock_element)
|
|
312
|
+
expect(result).to eq('r-else')
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
context 'when element has no conditional attributes' do
|
|
317
|
+
before do
|
|
318
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('class')
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it 'returns r-if as fallback' do
|
|
322
|
+
result = described_class.get_conditional_type(mock_element)
|
|
323
|
+
expect(result).to eq('r-if')
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
describe '.get_conditional_expression' do
|
|
329
|
+
let(:mock_element) { double('element') }
|
|
330
|
+
let(:mock_attributes) { double('attributes') }
|
|
331
|
+
let(:mock_attribute) { double('attribute') }
|
|
332
|
+
|
|
333
|
+
before do
|
|
334
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(mock_attributes)
|
|
335
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(1)
|
|
336
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
context 'when element has r-if attribute with embedded script' do
|
|
340
|
+
before do
|
|
341
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-if')
|
|
342
|
+
allow(mock_attribute).to receive(:[]).with(:value).and_return('{state[:visible]}')
|
|
343
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:embed_script?).with('{state[:visible]}').and_return(true)
|
|
344
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:get_embed_script).with('{state[:visible]}').and_return('state[:visible]')
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it 'returns the extracted script' do
|
|
348
|
+
result = described_class.get_conditional_expression(mock_element)
|
|
349
|
+
expect(result).to eq('state[:visible]')
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
context 'when element has r-elsif attribute with plain value' do
|
|
354
|
+
before do
|
|
355
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('r-elsif')
|
|
356
|
+
allow(mock_attribute).to receive(:[]).with(:value).and_return('condition')
|
|
357
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:embed_script?).with('condition').and_return(false)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
it 'returns the plain value' do
|
|
361
|
+
result = described_class.get_conditional_expression(mock_element)
|
|
362
|
+
expect(result).to eq('condition')
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
context 'when element has no conditional attributes' do
|
|
367
|
+
before do
|
|
368
|
+
allow(mock_attribute).to receive(:[]).with(:name).and_return('class')
|
|
369
|
+
allow(mock_attribute).to receive(:[]).with(:value).and_return('some-class')
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
it 'returns true as fallback' do
|
|
373
|
+
result = described_class.get_conditional_expression(mock_element)
|
|
374
|
+
expect(result).to eq('true')
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
describe '.filter_conditional_attributes' do
|
|
380
|
+
let(:mock_attributes) { double('attributes') }
|
|
381
|
+
let(:mock_attribute_1) { double('attribute_1') }
|
|
382
|
+
let(:mock_attribute_2) { double('attribute_2') }
|
|
383
|
+
let(:mock_attribute_3) { double('attribute_3') }
|
|
384
|
+
let(:mock_attribute_4) { double('attribute_4') }
|
|
385
|
+
|
|
386
|
+
context 'when attributes contain conditional and data-template attributes' do
|
|
387
|
+
before do
|
|
388
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(4)
|
|
389
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute_1)
|
|
390
|
+
allow(mock_attributes).to receive(:[]).with(1).and_return(mock_attribute_2)
|
|
391
|
+
allow(mock_attributes).to receive(:[]).with(2).and_return(mock_attribute_3)
|
|
392
|
+
allow(mock_attributes).to receive(:[]).with(3).and_return(mock_attribute_4)
|
|
393
|
+
|
|
394
|
+
allow(mock_attribute_1).to receive(:[]).with(:name).and_return('r-if')
|
|
395
|
+
allow(mock_attribute_2).to receive(:[]).with(:name).and_return('class')
|
|
396
|
+
allow(mock_attribute_3).to receive(:[]).with(:name).and_return('data-template')
|
|
397
|
+
allow(mock_attribute_4).to receive(:[]).with(:name).and_return('id')
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it 'filters out conditional and data-template attributes' do
|
|
401
|
+
result = described_class.filter_conditional_attributes(mock_attributes)
|
|
402
|
+
expect(result).to eq([mock_attribute_2, mock_attribute_4])
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
context 'when attributes contain only regular attributes' do
|
|
407
|
+
before do
|
|
408
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(2)
|
|
409
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute_1)
|
|
410
|
+
allow(mock_attributes).to receive(:[]).with(1).and_return(mock_attribute_2)
|
|
411
|
+
|
|
412
|
+
allow(mock_attribute_1).to receive(:[]).with(:name).and_return('class')
|
|
413
|
+
allow(mock_attribute_2).to receive(:[]).with(:name).and_return('id')
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
it 'returns all attributes' do
|
|
417
|
+
result = described_class.filter_conditional_attributes(mock_attributes)
|
|
418
|
+
expect(result).to eq([mock_attribute_1, mock_attribute_2])
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
context 'when attributes contain only conditional attributes' do
|
|
423
|
+
before do
|
|
424
|
+
allow(mock_attributes).to receive(:[]).with(:length).and_return(3)
|
|
425
|
+
allow(mock_attributes).to receive(:[]).with(0).and_return(mock_attribute_1)
|
|
426
|
+
allow(mock_attributes).to receive(:[]).with(1).and_return(mock_attribute_2)
|
|
427
|
+
allow(mock_attributes).to receive(:[]).with(2).and_return(mock_attribute_3)
|
|
428
|
+
|
|
429
|
+
allow(mock_attribute_1).to receive(:[]).with(:name).and_return('r-if')
|
|
430
|
+
allow(mock_attribute_2).to receive(:[]).with(:name).and_return('r-elsif')
|
|
431
|
+
allow(mock_attribute_3).to receive(:[]).with(:name).and_return('r-else')
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
it 'returns empty array' do
|
|
435
|
+
result = described_class.filter_conditional_attributes(mock_attributes)
|
|
436
|
+
expect(result).to eq([])
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
describe '.build_single_conditional_content' do
|
|
442
|
+
let(:mock_element) { double('element') }
|
|
443
|
+
let(:mock_attributes) { double('attributes') }
|
|
444
|
+
let(:filtered_attributes) { [] }
|
|
445
|
+
|
|
446
|
+
before do
|
|
447
|
+
allow(mock_element).to receive(:[]).with(:attributes).and_return(mock_attributes)
|
|
448
|
+
allow(described_class).to receive(:filter_conditional_attributes).with(mock_attributes).and_return(filtered_attributes)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
context 'when element is a template' do
|
|
452
|
+
before do
|
|
453
|
+
allow(mock_element).to receive(:[]).with(:tagName).and_return('TEMPLATE')
|
|
454
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:has_data_template_attribute?).with(mock_element).and_return(false)
|
|
455
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:build_fragment).with(mock_element, 'template').and_return('fragment_result')
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
it 'builds fragment' do
|
|
459
|
+
result = described_class.build_single_conditional_content(mock_element)
|
|
460
|
+
expect(result).to eq('fragment_result')
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
context 'when element is a div with data-template attribute' do
|
|
465
|
+
before do
|
|
466
|
+
allow(mock_element).to receive(:[]).with(:tagName).and_return('DIV')
|
|
467
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:has_data_template_attribute?).with(mock_element).and_return(true)
|
|
468
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:build_fragment).with(mock_element, 'div').and_return('fragment_result')
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
it 'builds fragment' do
|
|
472
|
+
result = described_class.build_single_conditional_content(mock_element)
|
|
473
|
+
expect(result).to eq('fragment_result')
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
context 'when element is a component' do
|
|
478
|
+
before do
|
|
479
|
+
allow(mock_element).to receive(:[]).with(:tagName).and_return('CUSTOM-COMPONENT')
|
|
480
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:has_data_template_attribute?).with(mock_element).and_return(false)
|
|
481
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:is_component?).with('custom-component').and_return(true)
|
|
482
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:build_component).with(mock_element, 'custom-component', filtered_attributes).and_return('component_result')
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
it 'builds component' do
|
|
486
|
+
result = described_class.build_single_conditional_content(mock_element)
|
|
487
|
+
expect(result).to eq('component_result')
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
context 'when element is a regular HTML element' do
|
|
492
|
+
before do
|
|
493
|
+
allow(mock_element).to receive(:[]).with(:tagName).and_return('DIV')
|
|
494
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:has_data_template_attribute?).with(mock_element).and_return(false)
|
|
495
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:is_component?).with('div').and_return(false)
|
|
496
|
+
allow(RubyWasmUi::Template::BuildVdom).to receive(:build_element).with(mock_element, 'div', filtered_attributes).and_return('element_result')
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
it 'builds element' do
|
|
500
|
+
result = described_class.build_single_conditional_content(mock_element)
|
|
501
|
+
expect(result).to eq('element_result')
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|