maplibre-preview 0.0.2 → 1.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.
@@ -0,0 +1,329 @@
1
+ doctype html
2
+ html
3
+ head
4
+ meta[charset="utf-8"]
5
+ title Map Preview
6
+ meta[name="viewport" content="width=device-width, initial-scale=1.0"]
7
+ link[rel="icon" type="image/png" href="/icons/favicon.png"]
8
+ link[href="https://unpkg.com/maplibre-gl@#{MapLibrePreview::MAPLIBRE_VERSION}/dist/maplibre-gl.css" rel='stylesheet']
9
+ script[src="https://unpkg.com/maplibre-gl@#{MapLibrePreview::MAPLIBRE_VERSION}/dist/maplibre-gl.js"]
10
+ script[src="https://unpkg.com/maplibre-contour@#{MapLibrePreview::CONTOUR_VERSION}/dist/index.min.js"]
11
+ script[src="https://d3js.org/d3.v#{MapLibrePreview::D3_VERSION}.min.js"]
12
+ script[src='/js/filters.js']
13
+ script[src='/js/contour.js']
14
+ style
15
+ | * { margin: 0; padding: 0; box-sizing: border-box; }
16
+ | html { height: 100%; }
17
+ | body {
18
+ | font-family: 'JetBrains Mono', 'Consolas', 'Monaco', monospace;
19
+ | background: #2b2b2b; color: #a9b7c6; line-height: 1.4; font-size: 14px;
20
+ | margin: 0; padding: 0; overflow: hidden;
21
+ | }
22
+ | .container { width: 100%; height: 100vh; margin: 0; padding: 0; }
23
+ | .header {
24
+ | background: #3c3f41; border: 1px solid #555555; padding: 20px;
25
+ | border-radius: 4px; margin-bottom: 20px;
26
+ | }
27
+ | .header h1 { color: #ffc66d; font-size: 20px; margin-bottom: 8px; }
28
+ | .header p { color: #808080; font-size: 13px; }
29
+ | .section {
30
+ | background: #3c3f41; border: 1px solid #555555; padding: 16px;
31
+ | border-radius: 4px; margin-bottom: 16px;
32
+ | }
33
+ | .section-title {
34
+ | color: #6a9955; font-weight: bold; margin-bottom: 12px; font-size: 16px;
35
+ | }
36
+ | .status-ok { color: #6a9955; }
37
+ | .status-error { color: #f44747; }
38
+ | .status-warning { color: #ffc66d; }
39
+ | .status-disabled { color: #808080; }
40
+ | .style-card {
41
+ | background: #313335; border: 1px solid #464749; padding: 12px;
42
+ | border-radius: 3px; margin-bottom: 16px;
43
+ | }
44
+ | .style-header { display: flex; justify-content: space-between; margin-bottom: 8px; }
45
+ | .style-name { color: #ffc66d; font-weight: bold; }
46
+ | .style-id { color: #808080; font-size: 12px; }
47
+ | .style-description { color: #a9b7c6; font-size: 12px; margin-bottom: 8px; }
48
+ | .style-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
49
+ | .stat-item { font-size: 12px; }
50
+ | .stat-label { color: #808080; }
51
+ | .stat-value { color: #6897bb; }
52
+ | .endpoint-container {
53
+ | display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
54
+ | padding: 2px 4px; border-radius: 2px; transition: all 0.2s ease; margin-left: 4px;
55
+ | }
56
+ | .endpoint-container:hover { background: #4b4d4f; }
57
+ | .endpoint-container.copy-success {
58
+ | background: #2d4a2d !important; border: 1px solid #6a9955;
59
+ | }
60
+ | .endpoint-url {
61
+ | color: #6897bb; font-family: 'Courier New', monospace; font-size: 11px;
62
+ | text-decoration: underline;
63
+ | }
64
+ | .copy-icon {
65
+ | color: #555555; font-size: 9px; flex-shrink: 0; transition: color 0.2s; opacity: 0.5;
66
+ | }
67
+ | .endpoint-container:hover .copy-icon { color: #808080; opacity: 0.8; }
68
+ | .copy-success .copy-icon { color: #6a9955 !important; opacity: 1 !important; }
69
+ | .style-actions { margin-top: 12px; }
70
+ | .btn {
71
+ | display: inline-block; padding: 6px 12px; background: #4b4d4f; color: #a9b7c6;
72
+ | text-decoration: none; border-radius: 3px; font-size: 12px;
73
+ | border: 1px solid #555555; margin-right: 8px;
74
+ | }
75
+ | .btn:hover { background: #5a5d5f; border-color: #666666; }
76
+ | .btn-primary {
77
+ | background: #6a9955; color: #ffffff; border-color: #7bb366;
78
+ | }
79
+ | .btn-primary:hover { background: #7bb366; border-color: #8cc477; }
80
+ | .btn-small { padding: 4px 8px; font-size: 10px; margin-right: 4px; }
81
+ | .refresh-btn {
82
+ | background: #ffc66d; color: #2b2b2b; border-color: #ffd87d; font-weight: bold;
83
+ | }
84
+ | .refresh-btn:hover { background: #ffd87d; border-color: #ffe88d; }
85
+ | .header-buttons { display: flex; gap: 8px; float: right; }
86
+ | .map-btn { background: #6897bb; color: #ffffff; border-color: #7aa8cc; }
87
+ | .map-btn:hover { background: #7aa8cc; border-color: #8bb9dd; }
88
+ | .sprite-section {
89
+ | margin-top: 12px; padding-top: 12px; border-top: 1px solid #464749;
90
+ | }
91
+ | .sprite-title { color: #6a9955; font-weight: bold; font-size: 12px; margin-bottom: 8px; }
92
+ | .sprite-endpoints { display: flex; flex-direction: column; gap: 6px; }
93
+ | .sprite-item { display: flex; align-items: center; gap: 8px; font-size: 11px; }
94
+ | .sprite-label { color: #808080; min-width: 40px; }
95
+ | .stats-grid {
96
+ | display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
97
+ | gap: 16px; margin-bottom: 20px;
98
+ | }
99
+ | .stat-box {
100
+ | background: #313335; border: 1px solid #464749; padding: 12px;
101
+ | border-radius: 3px; text-align: center;
102
+ | }
103
+ | .stat-number { font-size: 24px; font-weight: bold; color: #6897bb; margin-bottom: 4px; }
104
+ | .stat-desc { font-size: 12px; color: #808080; }
105
+ | .error-message { color: #f44747; font-size: 12px; margin-top: 4px; }
106
+ | .success-message { color: #6a9955; font-size: 12px; margin-top: 4px; }
107
+ | .performance-overlay {
108
+ | position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 1000;
109
+ | background: rgba(43, 43, 43, 0.95); border: 1px solid #555555;
110
+ | border-radius: 0 0 6px 6px; padding: 0; min-width: 400px;
111
+ | backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
112
+ | transition: all 0.3s ease;
113
+ | }
114
+ | .performance-toggle {
115
+ | position: absolute; top: 4px; right: 4px; z-index: 1001;
116
+ | background: none; border: none; color: #808080; cursor: pointer;
117
+ | font-size: 14px; padding: 0; width: 16px; height: 16px;
118
+ | display: flex; align-items: center; justify-content: center;
119
+ | border-radius: 2px; transition: all 0.2s ease;
120
+ | }
121
+ | .performance-toggle:hover {
122
+ | background: #4b4d4f; color: #a9b7c6;
123
+ | }
124
+ | .performance-content {
125
+ | padding: 8px 12px;
126
+ | }
127
+ | .metric-row {
128
+ | display: flex; justify-content: space-between; align-items: center;
129
+ | margin-bottom: 6px;
130
+ | }
131
+ | .metric-row:last-child { margin-bottom: 0; }
132
+ | .metric {
133
+ | display: flex; align-items: center; gap: 4px;
134
+ | font-size: 11px; flex: 1; justify-content: center;
135
+ | }
136
+ | .metric-label {
137
+ | color: #808080; font-weight: 500;
138
+ | }
139
+ | .metric-value {
140
+ | color: #6897bb; font-weight: bold; font-family: 'Courier New', monospace;
141
+ | min-width: 40px; text-align: center;
142
+ | }
143
+ | .metric-value.warning { color: #ffc66d; }
144
+ | .metric-value.error { color: #f44747; }
145
+ | .metric-value.success { color: #6a9955; }
146
+ | #map-container {
147
+ | position: relative; width: 100%; height: 100vh; background: #2b2b2b;
148
+ | }
149
+ | .map-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
150
+ | .controls {
151
+ | position: absolute; top: 10px; left: 10px; z-index: 1000;
152
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
153
+ | padding: 10px; border-radius: 4px; color: #a9b7c6;
154
+ | }
155
+ | .controls a { color: #6897bb; text-decoration: none; margin-right: 15px; }
156
+ | .controls a:hover { text-decoration: underline; }
157
+ | .layer-controls {
158
+ | position: absolute; top: 50%; left: 10px; transform: translateY(-50%); z-index: 1000;
159
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555; padding: 15px;
160
+ | border-radius: 4px; color: #a9b7c6; display: flex; flex-direction: column; gap: 6px;
161
+ | max-width: 300px; max-height: 80vh;
162
+ | }
163
+ | .mode-switcher {
164
+ | display: flex; gap: 2px; margin-bottom: 10px; background: #3c3f41;
165
+ | border-radius: 3px; padding: 2px; flex-shrink: 0;
166
+ | }
167
+ | .mode-button {
168
+ | background: #3c3f41; color: #808080; border: none; padding: 6px 12px;
169
+ | border-radius: 2px; cursor: pointer; font-size: 11px; transition: all 0.2s ease; flex: 1;
170
+ | }
171
+ | .mode-button:hover { background: #4b4d4f; color: #a9b7c6; }
172
+ | .mode-button.active { background: #6897bb; color: #ffffff; }
173
+ | .control-panel {
174
+ | display: none; flex-direction: column; gap: 6px;
175
+ | max-height: calc(80vh - 200px); overflow-y: auto;
176
+ | }
177
+ | .control-panel::-webkit-scrollbar { width: 8px; }
178
+ | .control-panel::-webkit-scrollbar-track { background: #3c3f41; border-radius: 4px; }
179
+ | .control-panel::-webkit-scrollbar-thumb {
180
+ | background: #555555; border-radius: 4px; border: 1px solid #3c3f41;
181
+ | }
182
+ | .control-panel::-webkit-scrollbar-thumb:hover { background: #666666; }
183
+ | .control-panel { scrollbar-width: thin; scrollbar-color: #555555 #3c3f41; }
184
+ | .control-panel.active { display: flex; }
185
+ | .control-panel-header {
186
+ | flex-shrink: 0; border-bottom: 1px solid #555555; padding-bottom: 8px; margin-bottom: 8px;
187
+ | display: flex; flex-direction: column; gap: 6px;
188
+ | }
189
+ | .control-panel-content {
190
+ | flex: 1; overflow-y: auto;
191
+ | }
192
+ | .control-button {
193
+ | background: #4b4d4f; color: #a9b7c6; border: 1px solid #555555;
194
+ | padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 11px;
195
+ | transition: all 0.2s ease; width: 100%; box-sizing: border-box;
196
+ | }
197
+ | .control-button:hover { background: #5a5d5f; border-color: #666666; }
198
+ | .control-button.active { background: #6a9955; color: #ffffff; border-color: #7bb366; }
199
+ | .control-button.inactive { background: #3c3f41; color: #808080; border-color: #464749; }
200
+ | .filter-group {
201
+ | margin-bottom: 8px;
202
+ | border: 1px solid #555555;
203
+ | border-radius: 3px;
204
+ | overflow: hidden;
205
+ | }
206
+ | .filter-group-button {
207
+ | width: 100%;
208
+ | font-weight: bold;
209
+ | background: #4b4d4f;
210
+ | border: none;
211
+ | border-bottom: 1px solid #555555;
212
+ | }
213
+ | .filter-sub-buttons {
214
+ | display: flex;
215
+ | flex-direction: column;
216
+ | gap: 2px;
217
+ | padding: 3px;
218
+ | background: #3c3f41;
219
+ | border: 1px solid #6a9955;
220
+ | border-radius: 2px;
221
+ | box-sizing: border-box;
222
+ | }
223
+ | .filter-sub-button {
224
+ | font-size: 10px;
225
+ | padding: 4px 8px;
226
+ | margin: 0;
227
+ | background: #ffc66d;
228
+ | color: #2b2b2b;
229
+ | border: 1px solid #e6b85a;
230
+ | border-radius: 2px;
231
+ | cursor: pointer;
232
+ | transition: all 0.2s ease;
233
+ | }
234
+ | .filter-sub-button:hover {
235
+ | background: #ffd87d;
236
+ | border-color: #f0c050;
237
+ | }
238
+ | .filter-sub-button.active {
239
+ | background: #ffd87d;
240
+ | color: #2b2b2b;
241
+ | border-color: #f0c050;
242
+ | font-weight: bold;
243
+ | }
244
+ | .filter-sub-button.inactive {
245
+ | background: #3c3f41;
246
+ | color: #808080;
247
+ | border-color: #464749;
248
+ | }
249
+
250
+ | .toggle-basemap {
251
+ | position: absolute; top: 10px; right: 10px; z-index: 1000;
252
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555; padding: 10px;
253
+ | border-radius: 4px; color: #a9b7c6; border: none; cursor: pointer;
254
+ | }
255
+ | .toggle-basemap:hover { background: rgba(75, 77, 79, 0.95); }
256
+ | #loading-indicator {
257
+ | position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
258
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
259
+ | border-radius: 6px; padding: 12px 20px; z-index: 1000;
260
+ | backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
261
+ | transition: all 0.3s ease; display: none;
262
+ | }
263
+ | .loading-progress { text-align: center; color: #a9b7c6; }
264
+ | .loading-text { font-size: 14px; color: #a9b7c6; margin-bottom: 8px; }
265
+ | .progress-bar {
266
+ | width: 200px; height: 4px; background: #3c3f41; border-radius: 2px;
267
+ | overflow: hidden; margin: 0 auto;
268
+ | }
269
+ | .progress-fill {
270
+ | height: 100%; background: #6897bb; border-radius: 2px;
271
+ | transition: width 0.3s ease; width: 0%;
272
+ | }
273
+ | /* MapLibre Controls */
274
+ | .maplibregl-ctrl-scale, .maplibregl-ctrl-group, .maplibregl-ctrl-terrain, .maplibregl-ctrl-globe {
275
+ | background: rgba(60, 63, 65, 0.95) !important; border: 1px solid #555555 !important;
276
+ | border-radius: 4px !important; backdrop-filter: blur(10px) !important;
277
+ | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
278
+ | }
279
+ | .maplibregl-ctrl-scale { color: rgb(100, 185, 61) !important; font-size: 11px !important; padding: 4px 8px !important; }
280
+ | .maplibregl-ctrl-scale-line { background: #6897bb !important; border: 1px solid #7aa8cc !important; }
281
+ | .maplibregl-ctrl-group button { background: rgba(75, 105, 61, 0.8) !important; border: 1px solid rgba(72, 139, 92, 0.4) !important; color: #ffffff !important; font-size: 14px !important; font-weight: bold !important; transition: all 0.2s ease !important; }
282
+ | .maplibregl-ctrl-group button:hover { background: rgba(255, 198, 109, 0.9) !important; border-color: #ffd87d !important; color: #2b2b2b !important; transform: scale(1.05) !important; }
283
+ | .maplibregl-ctrl-group button:active { background: rgba(106, 153, 85, 1) !important; transform: scale(0.95) !important; }
284
+ | .maplibregl-ctrl-terrain button { background: rgba(104, 151, 187, 0.8) !important; border: 1px solid #7aa8cc !important; color: #ffffff !important; font-size: 16px !important; font-weight: bold !important; transition: all 0.2s ease !important; padding: 8px !important; }
285
+ | .maplibregl-ctrl-terrain button:hover { background: rgba(104, 151, 187, 1) !important; border-color: #8bb9dd !important; transform: scale(1.1) !important; box-shadow: 0 2px 8px rgba(104, 151, 187, 0.4) !important; }
286
+ | .maplibregl-ctrl-terrain button:active { transform: scale(0.95) !important; }
287
+ | .maplibregl-ctrl-globe button { background: rgba(255, 198, 109, 0.8) !important; border: 1px solid #ffd87d !important; color: #2b2b2b !important; font-size: 16px !important; font-weight: bold !important; transition: all 0.2s ease !important; padding: 8px !important; }
288
+ | .maplibregl-ctrl-globe button:hover { background: rgba(255, 198, 109, 1) !important; border-color: #ffe88d !important; transform: scale(1.1) !important; box-shadow: 0 2px 8px rgba(255, 198, 109, 0.4) !important; }
289
+ | .maplibregl-ctrl-globe button:active { transform: scale(0.95) !important; }
290
+ | .profile-overlay {
291
+ | position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 1000;
292
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
293
+ | border-radius: 6px; padding: 12px; width: 70vw; max-width: 800px;
294
+ | backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
295
+ | }
296
+ | .profile-header {
297
+ | display: flex; justify-content: space-between; align-items: center;
298
+ | margin-bottom: 8px; border-bottom: 1px solid #555555; padding-bottom: 8px;
299
+ | }
300
+ | .profile-title { color: #ffc66d; font-weight: bold; font-size: 12px; }
301
+ | .profile-close {
302
+ | background: none; border: none; color: #808080; cursor: pointer;
303
+ | font-size: 14px; padding: 0; width: 16px; height: 16px;
304
+ | display: flex; align-items: center; justify-content: center;
305
+ | border-radius: 2px; transition: all 0.2s ease;
306
+ | }
307
+ | .profile-close:hover { background: #4b4d4f; color: #a9b7c6; }
308
+ | .profile-stats {
309
+ | display: flex; justify-content: space-between; margin-bottom: 8px;
310
+ | font-size: 10px; color: #808080;
311
+ | }
312
+ | .profile-chart { margin-top: 8px; }
313
+ | .elevation-tooltip {
314
+ | position: fixed; z-index: 1001; pointer-events: none;
315
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
316
+ | border-radius: 4px; padding: 8px 12px; font-size: 16px;
317
+ | color: #6a9955; backdrop-filter: blur(10px);
318
+ | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
319
+ | transform: translate(10px, -50%); margin-left: 10px;
320
+ | font-weight: bold;
321
+ | }
322
+ | .profile-area { fill: #4CAF50; opacity: 0.3; }
323
+ | .profile-line { fill: none; stroke: #2196F3; stroke-width: 2; }
324
+ | .profile-tooltip-line { stroke: #ff0000; stroke-width: 1; stroke-dasharray: 3,3; }
325
+ | .profile-tooltip-circle { fill: #ff0000; r: 4; }
326
+ | .profile-tooltip-text { text-anchor: middle; fill: #ff0000; font-size: 14px; }
327
+ body
328
+ .container
329
+ == yield
@@ -1,3 +1,114 @@
1
- require 'maplibre-preview/version'
2
- require 'maplibre-preview/sinatra_ext'
1
+ # frozen_string_literal: true
3
2
 
3
+ require 'sinatra/base'
4
+ require 'slim'
5
+ require 'rack'
6
+
7
+ module MapLibrePreview
8
+ # Fixed versions for guaranteed compatibility
9
+ MAPLIBRE_VERSION = '5.7.3'
10
+ CONTOUR_VERSION = '0.1.0'
11
+ D3_VERSION = '7'
12
+
13
+ # Rack middleware for serving static JS files from gem
14
+ class StaticMiddleware
15
+ def initialize(app)
16
+ @app = app
17
+ @gem_public_path = File.expand_path('maplibre-preview/public', __dir__)
18
+ end
19
+
20
+ def call(env)
21
+ request = Rack::Request.new(env)
22
+
23
+ if request.path.match?(%r{^/js/})
24
+ serve_js_file(request.path)
25
+ else
26
+ @app.call(env)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def serve_js_file(path)
33
+ file_path = File.join(@gem_public_path, path)
34
+
35
+ if File.exist?(file_path) && File.file?(file_path)
36
+ [200, {'Content-Type' => 'application/javascript'}, [File.read(file_path)]]
37
+ else
38
+ [404, {'Content-Type' => 'text/plain'}, ['File not found']]
39
+ end
40
+ end
41
+ end
42
+
43
+ # Sinatra extension for map development tools
44
+ module Extension
45
+ def self.registered(app)
46
+ app.helpers Helpers
47
+ app.use StaticMiddleware
48
+ app.set :maplibre_preview_options, {}
49
+ end
50
+ end
51
+
52
+ # Helper methods for map development tools
53
+ module Helpers
54
+ def render_maplibre_preview
55
+ gem_views_path = File.expand_path('maplibre-preview/views', __dir__)
56
+ original_views = settings.views
57
+ settings.set :views, gem_views_path
58
+ slim(:map, layout: :map_layout)
59
+ ensure
60
+ settings.set :views, original_views
61
+ end
62
+
63
+ def render_map_layout
64
+ gem_views_path = File.expand_path('maplibre-preview/views', __dir__)
65
+ original_views = settings.views
66
+
67
+ settings.set :views, gem_views_path
68
+ slim(:map_layout)
69
+ ensure
70
+ settings.set :views, original_views
71
+ end
72
+
73
+ def style_url
74
+ params[:style_url]
75
+ end
76
+
77
+ def should_show_map?
78
+ !!(params[:style] || params[:style_url] || params[:source])
79
+ end
80
+ end
81
+
82
+ # Standalone Sinatra application for map development
83
+ class App < Sinatra::Base
84
+ register Extension
85
+
86
+ configure do
87
+ set :views, File.expand_path('maplibre-preview/views', __dir__)
88
+ set :public_folder, File.expand_path('maplibre-preview/public', __dir__)
89
+ end
90
+
91
+ get '/js/:file' do
92
+ serve_js_file(params[:file])
93
+ end
94
+
95
+ get '/map' do
96
+ slim :map, layout: :map_layout, locals: { options: {} }
97
+ end
98
+
99
+ private
100
+
101
+ def serve_js_file(filename)
102
+ gem_js_path = File.expand_path("maplibre-preview/public/js/#{filename}", __dir__)
103
+ if File.exist?(gem_js_path)
104
+ content_type 'application/javascript'
105
+ File.read(gem_js_path)
106
+ else
107
+ status 404
108
+ 'File not found'
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ Sinatra.register MapLibrePreview::Extension
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MapLibrePreview do
6
+ describe 'App functionality' do
7
+ let(:app) { Class.new(MapLibrePreview::App) { set :environment, :test } }
8
+
9
+ it 'provides complete map development interface' do
10
+ get '/map'
11
+ expect(last_response).to be_ok
12
+
13
+ expect(last_response.body).to include('map-container')
14
+ expect(last_response.body).to include('maplibre-gl')
15
+ expect(last_response.body).to include('maplibre-contour')
16
+ expect(last_response.body).to include('d3')
17
+ end
18
+
19
+ it 'serves all required JavaScript modules' do
20
+ %w[filters.js contour.js].each do |js_file|
21
+ get "/js/#{js_file}"
22
+ expect(last_response).to be_ok
23
+ expect(last_response.content_type).to include('javascript')
24
+ expect(last_response.body).not_to be_empty
25
+ end
26
+ end
27
+ end
28
+
29
+ describe 'External dependencies' do
30
+ let(:app) { Class.new(MapLibrePreview::App) { set :environment, :test } }
31
+
32
+ it 'includes proper external dependencies' do
33
+ get '/map'
34
+ body = last_response.body
35
+
36
+ expect(body).to include('unpkg.com/maplibre-gl')
37
+ expect(body).to include('unpkg.com/maplibre-contour')
38
+ expect(body).to include('d3js.org/d3')
39
+ end
40
+ end
41
+
42
+ describe 'Extension integration' do
43
+ it 'provides extension for Sinatra integration' do
44
+ test_app = Class.new(Sinatra::Base) do
45
+ register MapLibrePreview::Extension
46
+ end
47
+
48
+ expect(test_app.ancestors).to include(MapLibrePreview::Extension)
49
+ expect(test_app.settings.maplibre_preview_options).to be_a(Hash)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'maplibre-preview'
5
+ require 'rspec'
6
+ require 'rack/test'
7
+
8
+ RSpec.configure do |config|
9
+ config.include Rack::Test::Methods
10
+ config.before { ENV['RACK_ENV'] = 'test' }
11
+ end