panda-core 0.7.5 ā 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 +4 -4
- data/Rakefile +0 -1
- data/lib/panda/core/asset_loader.rb +19 -5
- data/lib/panda/core/engine.rb +52 -0
- data/lib/panda/core/module_registry.rb +228 -0
- data/lib/panda/core/testing/rails_helper.rb +8 -1
- data/lib/panda/core/testing/support/system/capybara_setup.rb +62 -0
- data/lib/panda/core/testing/support/system/cuprite_setup.rb +115 -0
- data/lib/panda/core/testing/support/system/database_connection_helpers.rb +23 -0
- data/lib/panda/core/testing/support/system/system_test_helpers.rb +233 -0
- data/lib/panda/core/version.rb +1 -1
- data/lib/tasks/assets.rake +14 -4
- data/public/panda-core-assets/panda-core-0.7.5.css +2 -0
- data/public/panda-core-assets/panda-core-0.8.0.css +2 -0
- data/public/panda-core-assets/panda-core-1762886534.css +2 -0
- data/public/panda-core-assets/panda-core.css +1 -0
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d71882619f044e6c12c628c91dd32e1f3f8d35ed6c06c2d56a952df67b2d185d
|
|
4
|
+
data.tar.gz: 98ffdb33880b310143ba47d13f40edda3fe5568a9474d089fc56fa6f43b45cba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e76cc361df60d271c1ce4489eb892731e5ae03bdbe7de1eaddd9ae60c3bb9a0c31319aa309f6a8be020a9fae34cd1cbdcf3bfcce3ebc7bc8379609412b870167
|
|
7
|
+
data.tar.gz: a16db81da2dd0248dc6e0dd9d306dcc2d134ddcb372b707c65c99a9a6c31a24c79e175ee01412abe46fe6f6148afe2bd648351fa876cca3bfd6ec6d39c646a04
|
data/Rakefile
CHANGED
|
@@ -157,14 +157,28 @@ module Panda
|
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
def development_css_url
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
assets_dir = Panda::Core::Engine.root.join("public", "panda-core-assets")
|
|
161
|
+
|
|
162
|
+
# In dev/test, look for timestamp-based files (latest one)
|
|
163
|
+
if Rails.env.test? || Rails.env.development?
|
|
164
|
+
# Find all timestamp-based CSS files (exclude symlinks)
|
|
165
|
+
css_files = Dir[assets_dir.join("panda-core-*.css")].reject { |f| File.symlink?(f) }
|
|
166
|
+
|
|
167
|
+
if css_files.any?
|
|
168
|
+
# Return the most recently created file
|
|
169
|
+
latest = css_files.max_by { |f| File.basename(f)[/\d+/].to_i }
|
|
170
|
+
return "/panda-core-assets/#{File.basename(latest)}"
|
|
171
|
+
end
|
|
172
|
+
else
|
|
173
|
+
# In production, try versioned file first
|
|
174
|
+
version = asset_version
|
|
175
|
+
versioned_file = "/panda-core-assets/panda-core-#{version}.css"
|
|
176
|
+
return versioned_file if File.exist?(Rails.public_path.join("panda-core-assets", "panda-core-#{version}.css"))
|
|
177
|
+
end
|
|
164
178
|
|
|
165
179
|
# Fall back to unversioned file (always available from engine's public directory)
|
|
166
180
|
unversioned_file = "/panda-core-assets/panda-core.css"
|
|
167
|
-
return unversioned_file if File.exist?(
|
|
181
|
+
return unversioned_file if File.exist?(assets_dir.join("panda-core.css"))
|
|
168
182
|
|
|
169
183
|
nil
|
|
170
184
|
end
|
data/lib/panda/core/engine.rb
CHANGED
|
@@ -19,6 +19,10 @@ ensure
|
|
|
19
19
|
$stderr = original_stderr
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Load shared configuration modules
|
|
23
|
+
require_relative "shared/inflections_config"
|
|
24
|
+
require_relative "shared/generator_config"
|
|
25
|
+
|
|
22
26
|
# Load engine configuration modules
|
|
23
27
|
require_relative "engine/test_config"
|
|
24
28
|
require_relative "engine/autoload_config"
|
|
@@ -28,6 +32,9 @@ require_relative "engine/omniauth_config"
|
|
|
28
32
|
require_relative "engine/phlex_config"
|
|
29
33
|
require_relative "engine/admin_controller_config"
|
|
30
34
|
|
|
35
|
+
# Load module registry
|
|
36
|
+
require_relative "module_registry"
|
|
37
|
+
|
|
31
38
|
module Panda
|
|
32
39
|
module Core
|
|
33
40
|
class Engine < ::Rails::Engine
|
|
@@ -49,6 +56,51 @@ module Panda
|
|
|
49
56
|
initializer "panda_core.config" do |app|
|
|
50
57
|
# Configuration is already initialized with defaults in Configuration class
|
|
51
58
|
end
|
|
59
|
+
|
|
60
|
+
# Auto-compile CSS for test/development environments
|
|
61
|
+
initializer "panda_core.auto_compile_assets", after: :load_config_initializers do |app|
|
|
62
|
+
# Only auto-compile in test or when explicitly requested
|
|
63
|
+
next unless Rails.env.test? || ENV["PANDA_CORE_AUTO_COMPILE"] == "true"
|
|
64
|
+
|
|
65
|
+
# Use timestamp for cache busting in dev/test
|
|
66
|
+
timestamp = Time.now.to_i
|
|
67
|
+
assets_dir = Panda::Core::Engine.root.join("public", "panda-core-assets")
|
|
68
|
+
timestamped_css = assets_dir.join("panda-core-#{timestamp}.css")
|
|
69
|
+
|
|
70
|
+
# Check if any compiled CSS exists (timestamp-based)
|
|
71
|
+
existing_css = Dir[assets_dir.join("panda-core-*.css")].reject { |f| File.symlink?(f) }
|
|
72
|
+
|
|
73
|
+
if existing_css.empty?
|
|
74
|
+
warn "š¼ [Panda Core] Auto-compiling CSS for test environment..."
|
|
75
|
+
|
|
76
|
+
# Compile CSS with timestamp
|
|
77
|
+
require "open3"
|
|
78
|
+
require "fileutils"
|
|
79
|
+
|
|
80
|
+
FileUtils.mkdir_p(assets_dir)
|
|
81
|
+
|
|
82
|
+
# Get content paths from ModuleRegistry
|
|
83
|
+
content_paths = Panda::Core::ModuleRegistry.tailwind_content_paths
|
|
84
|
+
content_flags = content_paths.map { |path| "--content '#{path}'" }.join(" ")
|
|
85
|
+
|
|
86
|
+
# Compile directly to timestamped file with all registered module content
|
|
87
|
+
input_file = Panda::Core::Engine.root.join("app/assets/tailwind/application.css")
|
|
88
|
+
cmd = "bundle exec tailwindcss -i #{input_file} -o #{timestamped_css} #{content_flags} --minify"
|
|
89
|
+
|
|
90
|
+
_, stderr, status = Open3.capture3(cmd)
|
|
91
|
+
|
|
92
|
+
if status.success?
|
|
93
|
+
# Create unversioned symlink for fallback
|
|
94
|
+
symlink = assets_dir.join("panda-core.css")
|
|
95
|
+
FileUtils.rm_f(symlink) if File.exist?(symlink)
|
|
96
|
+
FileUtils.ln_sf(File.basename(timestamped_css), symlink)
|
|
97
|
+
|
|
98
|
+
warn "š¼ [Panda Core] CSS compilation successful (#{timestamped_css.size} bytes)"
|
|
99
|
+
else
|
|
100
|
+
warn "š¼ [Panda Core] CSS compilation failed: #{stderr}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
52
104
|
end
|
|
53
105
|
end
|
|
54
106
|
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
# Module registry for Panda ecosystem components
|
|
6
|
+
#
|
|
7
|
+
# This class maintains a registry of all Panda modules (CMS, CMS Pro, Community, etc.)
|
|
8
|
+
# and their asset paths. Each module self-registers during engine initialization.
|
|
9
|
+
#
|
|
10
|
+
# Benefits:
|
|
11
|
+
# - Core doesn't need hardcoded knowledge of other modules
|
|
12
|
+
# - Supports private modules (e.g., panda-community in development)
|
|
13
|
+
# - Single source of truth for asset compilation
|
|
14
|
+
# - Scales automatically to future modules
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# # In module's engine.rb (after class definition):
|
|
18
|
+
# Panda::Core::ModuleRegistry.register(
|
|
19
|
+
# gem_name: 'panda-cms',
|
|
20
|
+
# engine: 'Panda::CMS::Engine',
|
|
21
|
+
# paths: {
|
|
22
|
+
# views: 'app/views/panda/cms/**/*.erb',
|
|
23
|
+
# components: 'app/components/panda/cms/**/*.rb',
|
|
24
|
+
# stylesheets: 'app/assets/stylesheets/panda/cms/**/*.css'
|
|
25
|
+
# }
|
|
26
|
+
# )
|
|
27
|
+
#
|
|
28
|
+
class ModuleRegistry
|
|
29
|
+
@modules = {}
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
# Register a Panda module with its asset paths
|
|
33
|
+
#
|
|
34
|
+
# @param gem_name [String] Gem name (e.g., 'panda-cms')
|
|
35
|
+
# @param engine [String] Engine constant name (e.g., 'Panda::CMS::Engine')
|
|
36
|
+
# @param paths [Hash] Asset path patterns relative to engine root
|
|
37
|
+
# @option paths [String] :views View template paths
|
|
38
|
+
# @option paths [String] :components ViewComponent paths
|
|
39
|
+
# @option paths [String] :stylesheets Stylesheet paths (optional)
|
|
40
|
+
def register(gem_name:, engine:, paths:)
|
|
41
|
+
@modules[gem_name] = {
|
|
42
|
+
engine: engine,
|
|
43
|
+
paths: paths
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns all registered modules
|
|
48
|
+
#
|
|
49
|
+
# @return [Hash] Module registry
|
|
50
|
+
attr_reader :modules
|
|
51
|
+
|
|
52
|
+
# Returns content paths for Tailwind CSS scanning
|
|
53
|
+
#
|
|
54
|
+
# Tailwind needs to scan all files that might contain utility classes:
|
|
55
|
+
# - Views (ERB templates)
|
|
56
|
+
# - Components (ViewComponent classes)
|
|
57
|
+
# - JavaScript (Stimulus controllers, etc.)
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<String>] Full paths for Tailwind --content flags
|
|
60
|
+
def tailwind_content_paths
|
|
61
|
+
paths = []
|
|
62
|
+
|
|
63
|
+
# Core's own content (always included)
|
|
64
|
+
core_root = Panda::Core::Engine.root
|
|
65
|
+
paths << "#{core_root}/app/views/panda/core/**/*.erb"
|
|
66
|
+
paths << "#{core_root}/app/components/panda/core/**/*.rb"
|
|
67
|
+
|
|
68
|
+
# Registered modules (only if engine is loaded)
|
|
69
|
+
@modules.each do |gem_name, info|
|
|
70
|
+
next unless engine_available?(info[:engine])
|
|
71
|
+
|
|
72
|
+
root = engine_root(info[:engine])
|
|
73
|
+
next unless root
|
|
74
|
+
|
|
75
|
+
# Add configured path types
|
|
76
|
+
paths << "#{root}/#{info[:paths][:views]}" if info[:paths][:views]
|
|
77
|
+
paths << "#{root}/#{info[:paths][:components]}" if info[:paths][:components]
|
|
78
|
+
|
|
79
|
+
# For Tailwind scanning, we also need to scan JavaScript for utility classes
|
|
80
|
+
# Check if module has JavaScript (via importmap or direct paths)
|
|
81
|
+
js_root = root.join("app/javascript")
|
|
82
|
+
if js_root.directory?
|
|
83
|
+
paths << "#{js_root}/**/*.js"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Host application Panda overrides
|
|
88
|
+
# Applications can override any Panda views/components
|
|
89
|
+
if defined?(Rails.root)
|
|
90
|
+
paths << "#{Rails.root}/app/views/panda/**/*.erb"
|
|
91
|
+
paths << "#{Rails.root}/app/components/panda/**/*.rb"
|
|
92
|
+
paths << "#{Rails.root}/app/javascript/panda/**/*.js"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
paths.compact
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns JavaScript source files by introspecting importmaps
|
|
99
|
+
#
|
|
100
|
+
# Instead of duplicating file lists, we read the importmap configuration
|
|
101
|
+
# that each engine already maintains. This provides a single source of truth.
|
|
102
|
+
#
|
|
103
|
+
# @return [Array<String>] Full paths to JavaScript source files
|
|
104
|
+
def javascript_sources
|
|
105
|
+
return [] unless defined?(Rails.application&.importmap)
|
|
106
|
+
|
|
107
|
+
sources = []
|
|
108
|
+
|
|
109
|
+
# Detect importmap-rails version and use appropriate API
|
|
110
|
+
importmap = Rails.application.importmap
|
|
111
|
+
entries = if importmap.respond_to?(:packages)
|
|
112
|
+
# importmap-rails 2.x - packages is a hash
|
|
113
|
+
importmap.packages
|
|
114
|
+
elsif importmap.respond_to?(:entries)
|
|
115
|
+
# importmap-rails 1.x - entries is an array
|
|
116
|
+
importmap.entries.map { |e| [e.name, e] }.to_h
|
|
117
|
+
else
|
|
118
|
+
{}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Find all Panda-namespaced imports and resolve to file paths
|
|
122
|
+
entries.each do |name, config|
|
|
123
|
+
next unless name.to_s.match?(/^panda-/)
|
|
124
|
+
|
|
125
|
+
path = resolve_importmap_to_path(name, config)
|
|
126
|
+
sources << path if path
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
sources.compact.uniq
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Returns registered module names
|
|
133
|
+
#
|
|
134
|
+
# @return [Array<String>] List of registered gem names
|
|
135
|
+
def registered_modules
|
|
136
|
+
@modules.keys
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Check if a specific module is registered
|
|
140
|
+
#
|
|
141
|
+
# @param gem_name [String] Gem name to check
|
|
142
|
+
# @return [Boolean] True if module is registered
|
|
143
|
+
def registered?(gem_name)
|
|
144
|
+
@modules.key?(gem_name)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# Check if an engine constant is defined and available
|
|
150
|
+
#
|
|
151
|
+
# @param engine_name [String] Engine constant name
|
|
152
|
+
# @return [Boolean] True if engine is available
|
|
153
|
+
def engine_available?(engine_name)
|
|
154
|
+
Object.const_defined?(engine_name)
|
|
155
|
+
rescue NameError
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Get the root path of an engine
|
|
160
|
+
#
|
|
161
|
+
# @param engine_name [String] Engine constant name
|
|
162
|
+
# @return [Pathname, nil] Engine root path or nil if unavailable
|
|
163
|
+
def engine_root(engine_name)
|
|
164
|
+
return nil unless engine_available?(engine_name)
|
|
165
|
+
|
|
166
|
+
engine_class = Object.const_get(engine_name)
|
|
167
|
+
engine_class.root
|
|
168
|
+
rescue NoMethodError
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Resolve an importmap entry to an actual file path
|
|
173
|
+
#
|
|
174
|
+
# Importmap entries can be:
|
|
175
|
+
# - Direct paths: "panda/cms/controllers/dashboard_controller.js"
|
|
176
|
+
# - Module names: "panda-cms/controllers/dashboard_controller"
|
|
177
|
+
#
|
|
178
|
+
# We need to find where these files actually live in the filesystem.
|
|
179
|
+
#
|
|
180
|
+
# @param name [String, Symbol] Import name
|
|
181
|
+
# @param config [Object] Importmap entry (structure varies by version)
|
|
182
|
+
# @return [String, nil] Full file path or nil if not found
|
|
183
|
+
def resolve_importmap_to_path(name, config)
|
|
184
|
+
# Extract path from config (API differs between importmap-rails versions)
|
|
185
|
+
relative_path = if config.respond_to?(:path)
|
|
186
|
+
config.path
|
|
187
|
+
elsif config.respond_to?(:[])
|
|
188
|
+
config[:path] || config["path"]
|
|
189
|
+
elsif config.is_a?(String)
|
|
190
|
+
config
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
return nil unless relative_path
|
|
194
|
+
|
|
195
|
+
# Try to find the file in registered engines
|
|
196
|
+
@modules.each do |gem_name, info|
|
|
197
|
+
next unless engine_available?(info[:engine])
|
|
198
|
+
|
|
199
|
+
root = engine_root(info[:engine])
|
|
200
|
+
next unless root
|
|
201
|
+
|
|
202
|
+
# Check common JavaScript locations
|
|
203
|
+
[
|
|
204
|
+
root.join("app/javascript", relative_path),
|
|
205
|
+
root.join("app/javascript", "#{relative_path}.js"),
|
|
206
|
+
root.join("app/assets/javascripts", relative_path),
|
|
207
|
+
root.join("app/assets/javascripts", "#{relative_path}.js")
|
|
208
|
+
].each do |candidate|
|
|
209
|
+
return candidate.to_s if candidate.exist?
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Check Rails app if available
|
|
214
|
+
if defined?(Rails.root)
|
|
215
|
+
[
|
|
216
|
+
Rails.root.join("app/javascript", relative_path),
|
|
217
|
+
Rails.root.join("app/javascript", "#{relative_path}.js")
|
|
218
|
+
].each do |candidate|
|
|
219
|
+
return candidate.to_s if candidate.exist?
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -31,7 +31,14 @@ end
|
|
|
31
31
|
# Load all support files from panda-core
|
|
32
32
|
# Files are now in lib/panda/core/testing/support/ to be included in the published gem
|
|
33
33
|
support_path = File.expand_path("../support", __FILE__)
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
# Load system test infrastructure first (Capybara, Cuprite, helpers)
|
|
36
|
+
system_test_files = Dir[File.join(support_path, "system/**/*.rb")].sort
|
|
37
|
+
system_test_files.each { |f| require f }
|
|
38
|
+
|
|
39
|
+
# Load other support files
|
|
40
|
+
other_support_files = Dir[File.join(support_path, "**/*.rb")].sort.reject { |f| f.include?("/system/") }
|
|
41
|
+
other_support_files.each { |f| require f }
|
|
35
42
|
|
|
36
43
|
RSpec.configure do |config|
|
|
37
44
|
# Include panda-core route helpers by default
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared Capybara configuration for all Panda gems
|
|
4
|
+
# This provides standard Capybara setup with sensible defaults
|
|
5
|
+
|
|
6
|
+
# Increase wait time for CI environments where asset loading is slower
|
|
7
|
+
Capybara.default_max_wait_time = ENV["CI"].present? ? 10 : 5
|
|
8
|
+
|
|
9
|
+
# Normalize whitespaces when using `has_text?` and similar matchers,
|
|
10
|
+
# i.e., ignore newlines, trailing spaces, etc.
|
|
11
|
+
# That makes tests less dependent on slight UI changes.
|
|
12
|
+
Capybara.default_normalize_ws = true
|
|
13
|
+
|
|
14
|
+
# Where to store system tests artifacts (e.g. screenshots, downloaded files, etc.).
|
|
15
|
+
# It could be useful to be able to configure this path from the outside (e.g., on CI).
|
|
16
|
+
Capybara.save_path = ENV.fetch("CAPYBARA_ARTIFACTS", "./tmp/capybara")
|
|
17
|
+
|
|
18
|
+
# Disable animation so we're not waiting for it
|
|
19
|
+
Capybara.disable_animation = true
|
|
20
|
+
|
|
21
|
+
# See SystemTestHelpers#take_screenshot
|
|
22
|
+
# This allows us to track which session was last used for proper screenshot naming
|
|
23
|
+
Capybara.singleton_class.prepend(Module.new do
|
|
24
|
+
attr_accessor :last_used_session
|
|
25
|
+
|
|
26
|
+
def using_session(name, &block)
|
|
27
|
+
self.last_used_session = name
|
|
28
|
+
super
|
|
29
|
+
ensure
|
|
30
|
+
self.last_used_session = nil
|
|
31
|
+
end
|
|
32
|
+
end)
|
|
33
|
+
|
|
34
|
+
# Configure server host and port
|
|
35
|
+
Capybara.server_host = "127.0.0.1"
|
|
36
|
+
Capybara.server_port = ENV["CAPYBARA_PORT"]&.to_i # Let Capybara choose if not specified
|
|
37
|
+
|
|
38
|
+
# Configure Puma server with explicit options
|
|
39
|
+
# Use single-threaded mode to share database connection with tests
|
|
40
|
+
Capybara.register_server :puma do |app, port, host|
|
|
41
|
+
require "rack/handler/puma"
|
|
42
|
+
Rack::Handler::Puma.run(app, Port: port, Host: host, Silent: true, Threads: "1:1")
|
|
43
|
+
end
|
|
44
|
+
Capybara.server = :puma
|
|
45
|
+
|
|
46
|
+
# Do not set app_host here - let Capybara determine it from the server
|
|
47
|
+
# This avoids conflicts between what's configured and what's actually running
|
|
48
|
+
|
|
49
|
+
# RSpec configuration for Capybara
|
|
50
|
+
RSpec.configure do |config|
|
|
51
|
+
# Save screenshots on system test failures
|
|
52
|
+
config.after(:each, type: :system) do |example|
|
|
53
|
+
if example.exception
|
|
54
|
+
timestamp = Time.now.strftime("%Y-%m-%d-%H%M%S")
|
|
55
|
+
"tmp/capybara/failures/#{example.full_description.parameterize}_#{timestamp}"
|
|
56
|
+
|
|
57
|
+
# Screenshots are saved automatically by Capybara, but we could save additional artifacts here
|
|
58
|
+
# save_page("#{filename_base}.html")
|
|
59
|
+
# save_screenshot("#{filename_base}.png", full: true)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ferrum"
|
|
4
|
+
require "capybara/cuprite"
|
|
5
|
+
|
|
6
|
+
# Shared Cuprite driver configuration for all Panda gems
|
|
7
|
+
# This provides standard Cuprite setup with sensible defaults that work across gems
|
|
8
|
+
#
|
|
9
|
+
# Features:
|
|
10
|
+
# - :cuprite driver for standard desktop testing
|
|
11
|
+
# - :cuprite_mobile driver for mobile viewport testing
|
|
12
|
+
# - JavaScript error reporting enabled by default (js_errors: true)
|
|
13
|
+
# - CI-optimized browser options
|
|
14
|
+
# - Environment-based configuration (HEADLESS, INSPECTOR, SLOWMO)
|
|
15
|
+
|
|
16
|
+
module Panda
|
|
17
|
+
module Core
|
|
18
|
+
module Testing
|
|
19
|
+
module CupriteSetup
|
|
20
|
+
# Base Cuprite options shared across all drivers
|
|
21
|
+
def self.base_options
|
|
22
|
+
{
|
|
23
|
+
window_size: [1440, 1000],
|
|
24
|
+
inspector: ENV["INSPECTOR"].in?(%w[y 1 yes true]),
|
|
25
|
+
headless: !ENV["HEADLESS"].in?(%w[n 0 no false]),
|
|
26
|
+
slowmo: ENV["SLOWMO"]&.to_f || 0,
|
|
27
|
+
timeout: 30,
|
|
28
|
+
js_errors: true, # IMPORTANT: Report JavaScript errors as test failures
|
|
29
|
+
ignore_default_browser_options: false,
|
|
30
|
+
process_timeout: 10,
|
|
31
|
+
wait_for_network_idle: false, # Don't wait for all network requests
|
|
32
|
+
pending_connection_errors: false, # Don't fail on pending external connections
|
|
33
|
+
browser_options: {
|
|
34
|
+
"no-sandbox": nil,
|
|
35
|
+
"disable-gpu": nil,
|
|
36
|
+
"disable-dev-shm-usage": nil,
|
|
37
|
+
"disable-background-networking": nil,
|
|
38
|
+
"disable-default-apps": nil,
|
|
39
|
+
"disable-extensions": nil,
|
|
40
|
+
"disable-sync": nil,
|
|
41
|
+
"disable-translate": nil,
|
|
42
|
+
"no-first-run": nil,
|
|
43
|
+
"ignore-certificate-errors": nil,
|
|
44
|
+
"allow-insecure-localhost": nil,
|
|
45
|
+
"enable-features": "NetworkService,NetworkServiceInProcess",
|
|
46
|
+
"disable-blink-features": "AutomationControlled"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Additional options for CI environments
|
|
52
|
+
def self.ci_browser_options
|
|
53
|
+
{
|
|
54
|
+
"disable-web-security": nil,
|
|
55
|
+
"allow-file-access-from-files": nil,
|
|
56
|
+
"allow-file-access": nil
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Configure standard desktop driver
|
|
61
|
+
def self.register_desktop_driver
|
|
62
|
+
options = base_options.dup
|
|
63
|
+
|
|
64
|
+
# Add CI-specific options
|
|
65
|
+
if ENV["GITHUB_ACTIONS"] == "true"
|
|
66
|
+
options[:browser_options].merge!(ci_browser_options)
|
|
67
|
+
|
|
68
|
+
puts "\nš Cuprite Configuration (Desktop):"
|
|
69
|
+
puts " Debug mode: #{ENV["DEBUG"]}"
|
|
70
|
+
puts " Headless: #{options[:headless]}"
|
|
71
|
+
puts " JS Errors: #{options[:js_errors]}"
|
|
72
|
+
puts " Browser options: #{options[:browser_options].keys.join(", ")}"
|
|
73
|
+
puts ""
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
Capybara.register_driver :cuprite do |app|
|
|
77
|
+
Capybara::Cuprite::Driver.new(app, **options)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Configure mobile viewport driver
|
|
82
|
+
def self.register_mobile_driver
|
|
83
|
+
options = base_options.dup
|
|
84
|
+
options[:window_size] = [375, 667] # iPhone SE size
|
|
85
|
+
|
|
86
|
+
if ENV["GITHUB_ACTIONS"] == "true"
|
|
87
|
+
options[:browser_options].merge!(ci_browser_options)
|
|
88
|
+
|
|
89
|
+
puts "\nš Cuprite Configuration (Mobile):"
|
|
90
|
+
puts " Window size: #{options[:window_size]}"
|
|
91
|
+
puts " JS Errors: #{options[:js_errors]}"
|
|
92
|
+
puts ""
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
Capybara.register_driver :cuprite_mobile do |app|
|
|
96
|
+
Capybara::Cuprite::Driver.new(app, **options)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Register all drivers
|
|
101
|
+
def self.setup!
|
|
102
|
+
register_desktop_driver
|
|
103
|
+
register_mobile_driver
|
|
104
|
+
|
|
105
|
+
# Set default drivers
|
|
106
|
+
Capybara.default_driver = :cuprite
|
|
107
|
+
Capybara.javascript_driver = :cuprite
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Auto-setup when required
|
|
115
|
+
Panda::Core::Testing::CupriteSetup.setup!
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Share database connection between test thread and server thread
|
|
4
|
+
# This allows the Puma server to see uncommitted transaction data from fixtures
|
|
5
|
+
class ActiveRecord::Base
|
|
6
|
+
mattr_accessor :shared_connection
|
|
7
|
+
@@shared_connection = nil
|
|
8
|
+
|
|
9
|
+
def self.connection
|
|
10
|
+
@@shared_connection || retrieve_connection
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Set up shared connection before system tests
|
|
15
|
+
RSpec.configure do |config|
|
|
16
|
+
config.before(:each, type: :system) do
|
|
17
|
+
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
config.after(:each, type: :system) do
|
|
21
|
+
ActiveRecord::Base.shared_connection = nil
|
|
22
|
+
end
|
|
23
|
+
end
|