opal-rails 2.0.3 → 3.0.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 +4 -4
- data/.github/workflows/build.yml +39 -4
- data/.gitignore +5 -0
- data/Appraisals +20 -21
- data/CHANGELOG.md +36 -0
- data/Gemfile +2 -2
- data/PORTING.md +140 -0
- data/README.md +151 -88
- data/Rakefile +16 -2
- data/app/helpers/opal_helper.rb +9 -28
- data/bin/sandbox +3 -2
- data/bin/sandbox-setup +2 -0
- data/gemfiles/{rails_7_0_opal_1_0.gemfile → rails_7_0_opal_1_8.gemfile} +2 -2
- data/gemfiles/{rails_7_0_opal_1_3.gemfile → rails_7_0_opal_master.gemfile} +2 -2
- data/gemfiles/{rails_6_0_opal_1_0.gemfile → rails_8_0_opal_1_8.gemfile} +3 -3
- data/gemfiles/rails_8_0_opal_master.gemfile +10 -0
- data/gemfiles/{rails_6_1_opal_1_0.gemfile → rails_8_1_opal_1_8.gemfile} +3 -3
- data/gemfiles/rails_8_1_opal_master.gemfile +10 -0
- data/lib/generators/opal/assets/assets_generator.rb +11 -1
- data/lib/generators/opal/assets/templates/{javascript.js.rb → asset.rb.tt} +3 -6
- data/lib/generators/opal/install/install_generator.rb +160 -9
- data/lib/generators/opal/install/templates/{application.js.rb → application.rb} +4 -4
- data/lib/generators/opal/install/templates/dev.tt +8 -0
- data/lib/generators/opal/install/templates/{initializer.rb → initializer.rb.tt} +8 -0
- data/lib/opal/rails/builder_runner.rb +126 -0
- data/lib/opal/rails/engine.rb +11 -9
- data/lib/opal/rails/entrypoints_resolver.rb +81 -0
- data/lib/opal/rails/errors.rb +11 -0
- data/lib/opal/rails/file_watcher.rb +58 -0
- data/lib/opal/rails/haml6_filter.rb +6 -6
- data/lib/opal/rails/haml_filter.rb +3 -6
- data/lib/opal/rails/legacy_upgrade_warning.rb +95 -0
- data/lib/opal/rails/outputs_manifest.rb +82 -0
- data/lib/opal/rails/path_setup.rb +28 -0
- data/lib/opal/rails/task_hooks.rb +49 -0
- data/lib/opal/rails/version.rb +1 -1
- data/lib/opal/rails/watch_runner.rb +173 -0
- data/lib/opal/rails.rb +7 -0
- data/lib/tasks/opal.rake +48 -0
- data/opal-rails.gemspec +23 -14
- data/spec/end_to_end/full_lifecycle_spec.rb +377 -0
- data/spec/helpers/opal_helper_spec.rb +27 -34
- data/spec/integration/source_map_spec.rb +6 -8
- data/spec/opal/assets_generator_spec.rb +31 -0
- data/spec/opal/install_generator_spec.rb +140 -0
- data/spec/opal/rails/build_task_spec.rb +116 -0
- data/spec/opal/rails/builder_runner_spec.rb +151 -0
- data/spec/opal/rails/clobber_task_spec.rb +55 -0
- data/spec/opal/rails/entrypoints_resolver_spec.rb +50 -0
- data/spec/opal/rails/haml_filter_spec.rb +41 -0
- data/spec/opal/rails/legacy_upgrade_warning_spec.rb +94 -0
- data/spec/opal/rails/outputs_manifest_spec.rb +44 -0
- data/spec/opal/rails/path_setup_spec.rb +71 -0
- data/spec/opal/rails/task_hooks_spec.rb +61 -0
- data/spec/opal/rails/watch_runner_spec.rb +283 -0
- data/spec/opal/rails/watch_task_spec.rb +23 -0
- data/spec/spec_helper.rb +16 -7
- data/spec/support/browser_support_spec.rb +36 -0
- data/spec/support/capybara.rb +61 -0
- data/spec/support/reset_assets_cache.rb +9 -1
- data/spec/support/test_app.rb +23 -2
- data/test_apps/app/application_controller.rb +23 -32
- data/test_apps/app/assets/builds/.keep +0 -0
- data/test_apps/app/assets/config/manifest.js +3 -1
- data/test_apps/app/assets/images/.keep +0 -0
- data/test_apps/app/opal/application.rb +5 -0
- data/test_apps/app/{assets/javascripts/source_map_example.js.rb → opal/source_map_example.rb} +1 -2
- data/test_apps/app/opal/with_assignments.js.rb +8 -0
- data/test_apps/rails.rb +19 -5
- metadata +196 -50
- data/gemfiles/rails_6_0_opal_1_1.gemfile +0 -9
- data/gemfiles/rails_6_0_opal_1_3.gemfile +0 -10
- data/gemfiles/rails_6_0_opal_1_7.gemfile +0 -10
- data/gemfiles/rails_6_1_opal_1_1.gemfile +0 -9
- data/gemfiles/rails_6_1_opal_1_3.gemfile +0 -10
- data/gemfiles/rails_6_1_opal_1_7.gemfile +0 -10
- data/gemfiles/rails_7_0_opal_1_7.gemfile +0 -10
- data/lib/opal/rails/haml5_filter.rb +0 -28
- data/test_apps/app/assets/javascripts/application.js.rb +0 -7
- data/test_apps/app/assets/javascripts/bar.rb +0 -3
- data/test_apps/app/assets/javascripts/foo.js.rb +0 -3
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'opal/rails/file_watcher'
|
|
3
|
+
require 'opal/rails/watch_runner'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Opal::Rails::WatchRunner do
|
|
6
|
+
FakeBuilderRunner = Struct.new(:results, :calls) do
|
|
7
|
+
def build(entrypoints:)
|
|
8
|
+
calls << entrypoints
|
|
9
|
+
results.shift
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
FakeManifest = Struct.new(:prune_calls, :write_calls) do
|
|
14
|
+
def prune_stale!(outputs)
|
|
15
|
+
prune_calls << outputs
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def write!(outputs)
|
|
19
|
+
write_calls << outputs
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class FakeFileWatcher
|
|
24
|
+
class << self
|
|
25
|
+
attr_accessor :instances
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
self.instances = []
|
|
29
|
+
|
|
30
|
+
attr_reader :extra_directories, :files
|
|
31
|
+
|
|
32
|
+
def initialize(files:, extra_directories:, &callback)
|
|
33
|
+
@files = files
|
|
34
|
+
@extra_directories = extra_directories
|
|
35
|
+
@callback = callback
|
|
36
|
+
@started = false
|
|
37
|
+
@stopped = false
|
|
38
|
+
self.class.instances << self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def start
|
|
42
|
+
@started = true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def stop
|
|
46
|
+
@stopped = true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def started?
|
|
50
|
+
@started
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def stopped?
|
|
54
|
+
@stopped
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
let(:source_path) { Pathname('/tmp/app/opal') }
|
|
59
|
+
let(:append_path) { Pathname('/tmp/app/shared/opal') }
|
|
60
|
+
let(:config) do
|
|
61
|
+
ActiveSupport::OrderedOptions.new.tap do |opal|
|
|
62
|
+
opal.source_path = source_path
|
|
63
|
+
opal.entrypoints_path = source_path
|
|
64
|
+
opal.build_path = Pathname('/tmp/app/assets/builds')
|
|
65
|
+
opal.entrypoints = { 'application' => 'application.rb', 'admin' => 'admin.rb' }
|
|
66
|
+
opal.append_paths = [append_path]
|
|
67
|
+
opal.source_map_enabled = true
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
let(:resolver) { instance_double(Opal::Rails::EntrypointsResolver) }
|
|
71
|
+
let(:manifest) { FakeManifest.new([], []) }
|
|
72
|
+
let(:builder_runner) { FakeBuilderRunner.new(results.dup, []) }
|
|
73
|
+
let(:results) do
|
|
74
|
+
[
|
|
75
|
+
{
|
|
76
|
+
outputs: %w[application.js application.js.map admin.js admin.js.map],
|
|
77
|
+
dependencies: {
|
|
78
|
+
'application' => ['/tmp/app/opal/application.rb', '/tmp/app/opal/shared.rb'],
|
|
79
|
+
'admin' => ['/tmp/app/opal/admin.rb']
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
outputs: %w[application.js application.js.map],
|
|
84
|
+
dependencies: {
|
|
85
|
+
'application' => ['/tmp/app/opal/application.rb', '/tmp/app/opal/shared.rb']
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
outputs: %w[application.js application.js.map admin.js admin.js.map],
|
|
90
|
+
dependencies: {
|
|
91
|
+
'application' => ['/tmp/app/opal/application.rb', '/tmp/app/opal/shared.rb', '/tmp/app/opal/new_file.rb'],
|
|
92
|
+
'admin' => ['/tmp/app/opal/admin.rb']
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
before do
|
|
99
|
+
FakeFileWatcher.instances = []
|
|
100
|
+
allow(resolver).to receive(:resolve).and_return(
|
|
101
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' },
|
|
102
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' }
|
|
103
|
+
)
|
|
104
|
+
allow(Opal).to receive(:dependent_files).and_return(['/tmp/opal/corelib/runtime.rb'])
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'builds all entrypoints once and starts a watcher' do
|
|
108
|
+
runner = described_class.new(
|
|
109
|
+
config: config,
|
|
110
|
+
resolver: resolver,
|
|
111
|
+
builder_runner: builder_runner,
|
|
112
|
+
manifest: manifest,
|
|
113
|
+
file_watcher_class: FakeFileWatcher
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
runner.start!
|
|
117
|
+
|
|
118
|
+
expect(builder_runner.calls).to eq([
|
|
119
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' }
|
|
120
|
+
])
|
|
121
|
+
expect(manifest.prune_calls).to eq([
|
|
122
|
+
%w[admin.js admin.js.map application.js application.js.map]
|
|
123
|
+
])
|
|
124
|
+
expect(manifest.write_calls).to eq([
|
|
125
|
+
%w[admin.js admin.js.map application.js application.js.map]
|
|
126
|
+
])
|
|
127
|
+
expect(FakeFileWatcher.instances.last).to be_started
|
|
128
|
+
expect(FakeFileWatcher.instances.last.extra_directories).to eq([
|
|
129
|
+
source_path.expand_path.to_s,
|
|
130
|
+
append_path.expand_path.to_s
|
|
131
|
+
])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'rebuilds only affected entrypoints for known modified files' do
|
|
135
|
+
runner = described_class.new(
|
|
136
|
+
config: config,
|
|
137
|
+
resolver: resolver,
|
|
138
|
+
builder_runner: builder_runner,
|
|
139
|
+
manifest: manifest,
|
|
140
|
+
file_watcher_class: FakeFileWatcher
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
runner.start!
|
|
144
|
+
runner.process_changes(modified: ['/tmp/app/opal/shared.rb'], added: [], removed: [])
|
|
145
|
+
|
|
146
|
+
expect(builder_runner.calls).to eq([
|
|
147
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' },
|
|
148
|
+
{ 'application' => 'application.rb' }
|
|
149
|
+
])
|
|
150
|
+
expect(manifest.prune_calls.length).to eq(1)
|
|
151
|
+
expect(manifest.write_calls.last).to eq(%w[admin.js admin.js.map application.js application.js.map])
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'rebuilds all entrypoints when a new file appears' do
|
|
155
|
+
runner = described_class.new(
|
|
156
|
+
config: config,
|
|
157
|
+
resolver: resolver,
|
|
158
|
+
builder_runner: builder_runner,
|
|
159
|
+
manifest: manifest,
|
|
160
|
+
file_watcher_class: FakeFileWatcher
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
runner.start!
|
|
164
|
+
runner.process_changes(modified: [], added: ['/tmp/app/opal/new_file.rb'], removed: [])
|
|
165
|
+
|
|
166
|
+
expect(builder_runner.calls).to eq([
|
|
167
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' },
|
|
168
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' }
|
|
169
|
+
])
|
|
170
|
+
expect(manifest.prune_calls.length).to eq(2)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'watches configured append_paths for rebuild-triggering changes' do
|
|
174
|
+
runner = described_class.new(
|
|
175
|
+
config: config,
|
|
176
|
+
resolver: resolver,
|
|
177
|
+
builder_runner: builder_runner,
|
|
178
|
+
manifest: manifest,
|
|
179
|
+
file_watcher_class: FakeFileWatcher
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
runner.start!
|
|
183
|
+
runner.process_changes(modified: [], added: ['/tmp/app/shared/opal/new_helper.rb'], removed: [])
|
|
184
|
+
|
|
185
|
+
expect(builder_runner.calls).to eq([
|
|
186
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' },
|
|
187
|
+
{ 'application' => 'application.rb', 'admin' => 'admin.rb' }
|
|
188
|
+
])
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it 're-resolves :all entrypoints and watches the dedicated entrypoints path' do
|
|
192
|
+
all_config = ActiveSupport::OrderedOptions.new.tap do |opal|
|
|
193
|
+
opal.source_path = Pathname('/tmp/app/opal')
|
|
194
|
+
opal.entrypoints_path = Pathname('/tmp/app/opal/entrypoints')
|
|
195
|
+
opal.build_path = Pathname('/tmp/app/assets/builds')
|
|
196
|
+
opal.entrypoints = :all
|
|
197
|
+
opal.append_paths = []
|
|
198
|
+
opal.source_map_enabled = true
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
all_resolver = instance_double(Opal::Rails::EntrypointsResolver)
|
|
202
|
+
allow(all_resolver).to receive(:resolve).and_return(
|
|
203
|
+
{ 'application' => 'application.rb' },
|
|
204
|
+
{ 'application' => 'application.rb', 'dashboard' => 'dashboard.rb' }
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
all_builder_runner = FakeBuilderRunner.new(
|
|
208
|
+
[
|
|
209
|
+
{
|
|
210
|
+
outputs: %w[application.js application.js.map],
|
|
211
|
+
dependencies: {
|
|
212
|
+
'application' => ['/tmp/app/opal/entrypoints/application.rb', '/tmp/app/opal/shared.rb']
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
outputs: %w[application.js application.js.map dashboard.js dashboard.js.map],
|
|
217
|
+
dependencies: {
|
|
218
|
+
'application' => ['/tmp/app/opal/entrypoints/application.rb', '/tmp/app/opal/shared.rb'],
|
|
219
|
+
'dashboard' => ['/tmp/app/opal/entrypoints/dashboard.rb']
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
[]
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
all_manifest = FakeManifest.new([], [])
|
|
227
|
+
|
|
228
|
+
runner = described_class.new(
|
|
229
|
+
config: all_config,
|
|
230
|
+
resolver: all_resolver,
|
|
231
|
+
builder_runner: all_builder_runner,
|
|
232
|
+
manifest: all_manifest,
|
|
233
|
+
file_watcher_class: FakeFileWatcher
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
runner.start!
|
|
237
|
+
expect(FakeFileWatcher.instances.last.extra_directories).to eq([
|
|
238
|
+
'/tmp/app/opal',
|
|
239
|
+
'/tmp/app/opal/entrypoints'
|
|
240
|
+
])
|
|
241
|
+
|
|
242
|
+
runner.process_changes(modified: [], added: ['/tmp/app/opal/entrypoints/dashboard.rb'], removed: [])
|
|
243
|
+
|
|
244
|
+
expect(all_builder_runner.calls).to eq([
|
|
245
|
+
{ 'application' => 'application.rb' },
|
|
246
|
+
{ 'application' => 'application.rb', 'dashboard' => 'dashboard.rb' }
|
|
247
|
+
])
|
|
248
|
+
expect(all_manifest.prune_calls).to eq([
|
|
249
|
+
%w[application.js application.js.map],
|
|
250
|
+
%w[application.js application.js.map dashboard.js dashboard.js.map]
|
|
251
|
+
])
|
|
252
|
+
expect(all_manifest.write_calls.last).to eq(%w[application.js application.js.map dashboard.js dashboard.js.map])
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'reports build errors without crashing the watcher' do
|
|
256
|
+
error_output = StringIO.new
|
|
257
|
+
|
|
258
|
+
failing_builder = FakeBuilderRunner.new(
|
|
259
|
+
[
|
|
260
|
+
results.first.dup,
|
|
261
|
+
nil # will not be reached; the second build raises
|
|
262
|
+
],
|
|
263
|
+
[]
|
|
264
|
+
)
|
|
265
|
+
allow(failing_builder).to receive(:build).and_call_original
|
|
266
|
+
allow(failing_builder).to receive(:build).with(entrypoints: { 'application' => 'application.rb' })
|
|
267
|
+
.and_raise(StandardError, 'syntax error in application.rb')
|
|
268
|
+
|
|
269
|
+
runner = described_class.new(
|
|
270
|
+
config: config,
|
|
271
|
+
resolver: resolver,
|
|
272
|
+
builder_runner: failing_builder,
|
|
273
|
+
manifest: manifest,
|
|
274
|
+
file_watcher_class: FakeFileWatcher,
|
|
275
|
+
error_output: error_output
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
runner.start!
|
|
279
|
+
expect { runner.process_changes(modified: ['/tmp/app/opal/shared.rb'], added: [], removed: []) }.not_to raise_error
|
|
280
|
+
expect(error_output.string).to include('Opal build error: syntax error in application.rb')
|
|
281
|
+
expect(FakeFileWatcher.instances.last).to be_started
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'opal/rails/file_watcher'
|
|
4
|
+
require 'opal/rails/watch_runner'
|
|
5
|
+
|
|
6
|
+
RSpec.describe 'opal:watch task' do
|
|
7
|
+
before do
|
|
8
|
+
Rake.application = Rake::Application.new
|
|
9
|
+
Rails.application.load_tasks
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after do
|
|
13
|
+
Rake.application = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'delegates to the watch runner' do
|
|
17
|
+
runner = instance_double(Opal::Rails::WatchRunner, watch: true)
|
|
18
|
+
expect(Opal::Rails::WatchRunner).to receive(:new).with(config: Rails.application.config.opal).at_least(:once).and_return(runner)
|
|
19
|
+
|
|
20
|
+
Rake::Task['opal:watch'].reenable
|
|
21
|
+
Rake::Task['opal:watch'].invoke
|
|
22
|
+
end
|
|
23
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
File.expand_path('..', __dir__)
|
|
2
2
|
|
|
3
3
|
require 'support/test_app'
|
|
4
4
|
require 'rspec/rails'
|
|
@@ -38,7 +38,7 @@ RSpec.configure do |config|
|
|
|
38
38
|
# ...rather than:
|
|
39
39
|
# # => "be bigger than 2"
|
|
40
40
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
41
|
-
expectations.syntax = [
|
|
41
|
+
expectations.syntax = %i[expect should]
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# rspec-mocks config goes here. You can use an alternate test double
|
|
@@ -98,19 +98,28 @@ RSpec.configure do |config|
|
|
|
98
98
|
# Configure Capybara
|
|
99
99
|
Capybara.javascript_driver = :cuprite
|
|
100
100
|
Capybara.register_driver(:cuprite) do |app|
|
|
101
|
-
|
|
101
|
+
driver_options = {
|
|
102
102
|
window_size: [1200, 800],
|
|
103
|
-
browser_options: {
|
|
103
|
+
browser_options: {
|
|
104
|
+
'disable-dev-shm-usage': nil,
|
|
105
|
+
'no-sandbox': nil
|
|
106
|
+
},
|
|
104
107
|
inspector: ENV['INSPECTOR'],
|
|
105
108
|
headless: !ENV['NO_HEADLESS'],
|
|
109
|
+
process_timeout: 30,
|
|
106
110
|
timeout: 20,
|
|
107
|
-
url_blacklist: []
|
|
108
|
-
|
|
111
|
+
url_blacklist: []
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
browser_path = BrowserSupport.path
|
|
115
|
+
driver_options[:browser_path] = browser_path if browser_path
|
|
116
|
+
|
|
117
|
+
Capybara::Cuprite::Driver.new(app, **driver_options)
|
|
109
118
|
end
|
|
110
119
|
|
|
111
120
|
Capybara.register_server :puma do |app, port, host|
|
|
112
121
|
require 'rack/handler/puma'
|
|
113
|
-
Rack::Handler::Puma.run(app, Host: host, Port: port, Threads:
|
|
122
|
+
Rack::Handler::Puma.run(app, Host: host, Port: port, Threads: '0:4', Silent: true)
|
|
114
123
|
end
|
|
115
124
|
|
|
116
125
|
Capybara.default_max_wait_time = 5
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe BrowserSupport do
|
|
4
|
+
describe '.path' do
|
|
5
|
+
it 'prefers an executable BROWSER_PATH override' do
|
|
6
|
+
allow(ENV).to receive(:[]).and_call_original
|
|
7
|
+
allow(ENV).to receive(:[]).with('BROWSER_PATH').and_return('/custom/browser')
|
|
8
|
+
allow(File).to receive(:executable?).and_call_original
|
|
9
|
+
allow(File).to receive(:executable?).with('/custom/browser').and_return(true)
|
|
10
|
+
|
|
11
|
+
expect(described_class.path).to eq('/custom/browser')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'falls back to known absolute browser paths' do
|
|
15
|
+
stub_const('BrowserSupport::ABSOLUTE_BROWSER_CANDIDATES', ['/custom/headless_shell'])
|
|
16
|
+
allow(ENV).to receive(:[]).and_call_original
|
|
17
|
+
allow(ENV).to receive(:[]).with('BROWSER_PATH').and_return(nil)
|
|
18
|
+
allow(ENV).to receive(:fetch).and_call_original
|
|
19
|
+
allow(ENV).to receive(:fetch).with('PATH', '').and_return('')
|
|
20
|
+
allow(File).to receive(:executable?).and_call_original
|
|
21
|
+
allow(File).to receive(:executable?).with('/custom/headless_shell').and_return(true)
|
|
22
|
+
|
|
23
|
+
expect(described_class.path).to eq('/custom/headless_shell')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '.available?' do
|
|
28
|
+
it 'reflects whether a browser path was found' do
|
|
29
|
+
allow(described_class).to receive(:path).and_return('/custom/browser')
|
|
30
|
+
expect(described_class.available?).to eq(true)
|
|
31
|
+
|
|
32
|
+
allow(described_class).to receive(:path).and_return(nil)
|
|
33
|
+
expect(described_class.available?).to eq(false)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/spec/support/capybara.rb
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
require 'capybara/rspec'
|
|
2
2
|
require 'capybara/cuprite'
|
|
3
3
|
|
|
4
|
+
module BrowserSupport
|
|
5
|
+
BROWSER_CANDIDATES = %w[
|
|
6
|
+
chromium
|
|
7
|
+
chromium-browser
|
|
8
|
+
google-chrome
|
|
9
|
+
google-chrome-stable
|
|
10
|
+
chrome
|
|
11
|
+
headless_shell
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
ABSOLUTE_BROWSER_CANDIDATES = %w[
|
|
15
|
+
/usr/lib64/chromium-browser/headless_shell
|
|
16
|
+
/usr/lib/chromium-browser/headless_shell
|
|
17
|
+
/usr/lib/chromium/headless_shell
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def path
|
|
23
|
+
env_path = ENV['BROWSER_PATH']
|
|
24
|
+
return env_path if env_path && File.executable?(env_path)
|
|
25
|
+
|
|
26
|
+
discovered_path = ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).find do |directory|
|
|
27
|
+
BROWSER_CANDIDATES.any? do |name|
|
|
28
|
+
File.executable?(File.join(directory, name))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
return File.join(discovered_path, discovered_executable_name(discovered_path)) if discovered_path
|
|
33
|
+
|
|
34
|
+
ABSOLUTE_BROWSER_CANDIDATES.find { |candidate| File.executable?(candidate) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def available?
|
|
38
|
+
!path.nil?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def discovered_executable_name(directory)
|
|
42
|
+
BROWSER_CANDIDATES.find do |name|
|
|
43
|
+
File.executable?(File.join(directory, name))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
4
48
|
module OpalHelper
|
|
5
49
|
def compile_opal(code)
|
|
6
50
|
Opal.compile(code, requireable: false)
|
|
@@ -32,6 +76,7 @@ module OpalHelper
|
|
|
32
76
|
|
|
33
77
|
until evaluate_script('document.readyState') == 'complete'
|
|
34
78
|
raise "reached max time (#{expire_in}s)" if timer.expired?
|
|
79
|
+
|
|
35
80
|
sleep 0.01
|
|
36
81
|
end
|
|
37
82
|
end
|
|
@@ -41,6 +86,7 @@ module OpalHelper
|
|
|
41
86
|
|
|
42
87
|
until evaluate_script('!!(window.Opal && window.Opal.gvars.ready)')
|
|
43
88
|
raise "reached max time (#{expire_in}s)" if timer.expired?
|
|
89
|
+
|
|
44
90
|
sleep 0.01
|
|
45
91
|
end
|
|
46
92
|
end
|
|
@@ -52,4 +98,19 @@ end
|
|
|
52
98
|
|
|
53
99
|
RSpec.configure do |config|
|
|
54
100
|
config.include OpalHelper
|
|
101
|
+
|
|
102
|
+
browser_path = BrowserSupport.path
|
|
103
|
+
|
|
104
|
+
config.before(:each, js: true) do
|
|
105
|
+
skip 'Browser-dependent spec skipped because no browser binary is available' unless BrowserSupport.available?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
config.before(:suite) do
|
|
109
|
+
ENV['BROWSER_PATH'] ||= browser_path if browser_path
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
config.append_after(:each, js: true) do
|
|
113
|
+
session = Capybara.current_session
|
|
114
|
+
session.driver.restart if session.driver.is_a?(Capybara::Cuprite::Driver)
|
|
115
|
+
end
|
|
55
116
|
end
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
2
|
|
|
3
3
|
RSpec.configure do |config|
|
|
4
|
-
config.before(:suite)
|
|
4
|
+
config.before(:suite) do
|
|
5
|
+
FileUtils.rmtree(Rails.root.join('tmp/cache/assets').to_s)
|
|
6
|
+
TestAppAssets.clobber!
|
|
7
|
+
TestAppAssets.build!
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
config.after(:suite) do
|
|
11
|
+
TestAppAssets.clobber!
|
|
12
|
+
end
|
|
5
13
|
end
|
data/spec/support/test_app.rb
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
|
+
require 'logger'
|
|
1
2
|
require 'rails'
|
|
2
|
-
ENV[
|
|
3
|
+
ENV['RAILS_ENV'] = 'test'
|
|
3
4
|
ENV['DATABASE_URL'] = 'sqlite3::memory:'
|
|
4
|
-
require_relative
|
|
5
|
+
require_relative '../../test_apps/rails'
|
|
5
6
|
|
|
7
|
+
module TestAppAssets
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def build!
|
|
11
|
+
config = Rails.application.config.opal
|
|
12
|
+
resolved_entrypoints = Opal::Rails::EntrypointsResolver.new(
|
|
13
|
+
entrypoints_path: config.entrypoints_path,
|
|
14
|
+
entrypoints: config.entrypoints
|
|
15
|
+
).resolve
|
|
16
|
+
|
|
17
|
+
result = Opal::Rails::BuilderRunner.new(config: config).build(entrypoints: resolved_entrypoints)
|
|
18
|
+
manifest = Opal::Rails::OutputsManifest.new(build_path: config.build_path)
|
|
19
|
+
manifest.prune_stale!(result[:outputs])
|
|
20
|
+
manifest.write!(result[:outputs])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def clobber!
|
|
24
|
+
Opal::Rails::OutputsManifest.new(build_path: Rails.application.config.opal.build_path).clobber!
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -1,61 +1,52 @@
|
|
|
1
|
-
LAYOUT =
|
|
2
|
-
<!DOCTYPE html>
|
|
3
|
-
<html>
|
|
4
|
-
<head><%= javascript_include_tag "application" %></head>
|
|
5
|
-
<body><%= yield %></body>
|
|
6
|
-
</html>
|
|
1
|
+
LAYOUT = <<~HTML
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head><%= ActionController::Base.helpers.javascript_include_tag "application" %></head>
|
|
5
|
+
<body><%= yield %></body>
|
|
6
|
+
</html>
|
|
7
7
|
HTML
|
|
8
8
|
|
|
9
|
-
INDEX =
|
|
10
|
-
<script type="text/ruby">
|
|
11
|
-
puts 'hello from a script tag!'
|
|
12
|
-
</script>
|
|
9
|
+
INDEX = <<~HTML
|
|
10
|
+
<script type="text/ruby">
|
|
11
|
+
puts 'hello from a script tag!'
|
|
12
|
+
</script>
|
|
13
13
|
HTML
|
|
14
14
|
|
|
15
15
|
HAML = <<~HAML
|
|
16
|
-
:opal
|
|
17
|
-
|
|
16
|
+
:opal
|
|
17
|
+
$haml_filter = :working
|
|
18
18
|
HAML
|
|
19
19
|
|
|
20
|
-
WITH_ASSIGNMENTS = File.read "#{__dir__}/
|
|
21
|
-
|
|
22
|
-
require_relative '../../app/helpers/opal_helper'
|
|
20
|
+
WITH_ASSIGNMENTS = File.read "#{__dir__}/opal/with_assignments.js.rb"
|
|
23
21
|
|
|
24
22
|
class ApplicationController < ActionController::Base
|
|
25
23
|
include Rails.application.routes.url_helpers
|
|
26
|
-
helper OpalHelper
|
|
27
24
|
layout 'application'
|
|
28
25
|
self.view_paths = [ActionView::FixtureResolver.new(
|
|
29
|
-
'layouts/application.html.erb'
|
|
30
|
-
'application/index.html.erb'
|
|
31
|
-
'application/haml_filter.html.haml'
|
|
32
|
-
'application/with_assignments.js.opal' => WITH_ASSIGNMENTS
|
|
26
|
+
'layouts/application.html.erb' => LAYOUT,
|
|
27
|
+
'application/index.html.erb' => INDEX,
|
|
28
|
+
'application/haml_filter.html.haml' => HAML,
|
|
29
|
+
'application/with_assignments.js.opal' => WITH_ASSIGNMENTS
|
|
33
30
|
)]
|
|
34
31
|
|
|
35
|
-
def index
|
|
36
|
-
end
|
|
32
|
+
def index; end
|
|
37
33
|
|
|
38
34
|
def with_assignments
|
|
39
35
|
object = Object.new
|
|
40
|
-
def object.as_json
|
|
41
|
-
{contents: 'json representation'}
|
|
36
|
+
def object.as_json(_options = {})
|
|
37
|
+
{ contents: 'json representation' }
|
|
42
38
|
end
|
|
43
39
|
|
|
44
40
|
@number_var = 1234
|
|
45
41
|
@string_var = 'hello'
|
|
46
|
-
@array_var = [1,'a']
|
|
47
|
-
@hash_var = {a: 1, b: 2}
|
|
42
|
+
@array_var = [1, 'a']
|
|
43
|
+
@hash_var = { a: 1, b: 2 }
|
|
48
44
|
@object_var = object
|
|
49
45
|
|
|
50
46
|
render type: :js, locals: { local_var: 'i am local' }
|
|
51
47
|
end
|
|
52
48
|
|
|
53
|
-
def haml_filter
|
|
54
|
-
end
|
|
49
|
+
def haml_filter; end
|
|
55
50
|
end
|
|
56
51
|
|
|
57
52
|
Rails.logger = Logger.new(STDOUT) if $DEBUG
|
|
58
|
-
|
|
59
|
-
require 'opal/sprockets/version'
|
|
60
|
-
p rails: Rails.version, opal: Opal::VERSION, ruby: RUBY_VERSION, opal_sprockets: Opal::Sprockets::VERSION
|
|
61
|
-
|
|
File without changes
|
|
File without changes
|