factory_bot_instrumentation 2.9.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 816705309be49f08f806e7c42795df561d37e3a4923d87456425f7c1d92984b4
4
- data.tar.gz: f78e531c94adfeeda2e97f6aeb7f47e22930cf9b38c12fc71052c2680f24aee1
3
+ metadata.gz: 3a38fbf7fbe98fed861a4fc524a10e5798e8e1abc918c742272365b11c476e81
4
+ data.tar.gz: a9b886fd2f52d856c264b34d8b2d4297df8ea12d1bb6bf1f84b1bbb6480b54ce
5
5
  SHA512:
6
- metadata.gz: 8ffec26066e0396cf9d61936e324c7103dd1353e684eb0ef073f9f3178e22f94ba9c6077f298b47140c007e9e8992e79550cc89903a7bc4c73ff6275e9bf19da
7
- data.tar.gz: ffbb12feb2054cd33e2ff0fe48e2a4bbb58abc3d8ef4ba66f5c3568897977fec578efc5e5ba484300d8d27ce812f5c20c3b2a2fa4fa959fdec4cba5e8670f884
6
+ metadata.gz: 0bbbb1aaad25753d40cd97dfdd968c9935cfc9d6aded00ceb39aded62067707ebde83364a6b688700bf2cef4c3b43778a3c67c6f54940224a2d6be237aec48bb
7
+ data.tar.gz: 412673861a682f7506c9b56e2be255f56ac66eb604596348de122e29e6aacd6cc54dcbbc6f51ec3eee1dd4c35b30dc2494f96e6b995678245c0d8ac54b275ec1
data/.gitignore CHANGED
@@ -11,6 +11,7 @@
11
11
  /gemfiles/vendor/
12
12
  /Gemfile.lock
13
13
  *.gemfile.lock
14
+ /.claude
14
15
 
15
16
  # rspec failure tracking
16
17
  .rspec_status
data/.rubocop.yml CHANGED
@@ -39,3 +39,7 @@ Rails/FilePath:
39
39
  # Because we just implemented the ActiveRecord API.
40
40
  Rails/SkipsModelValidations:
41
41
  Enabled: false
42
+
43
+ # Because we don't have a Rails environment here.
44
+ Rails/RakeEnvironment:
45
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  * TODO: Replace this bullet point with an actual description of a change.
4
4
 
5
+ ### 3.0.0 (13 May 2026)
6
+
7
+ * Added automatic asset registering with Sprockets ([#46](https://github.com/hausgold/factory_bot_instrumentation/pull/46))
8
+
9
+ So you can remove the following lines from your `app/assets/config/manifest.js`:
10
+
11
+ ```js
12
+ //= link factory_bot_instrumentation/application.css
13
+ //= link factory_bot_instrumentation/application.js
14
+ ```
15
+
16
+ * Added automatic asset bundling to support Propshaft ([#46](https://github.com/hausgold/factory_bot_instrumentation/pull/46))
17
+
5
18
  ### 2.9.0 (4 May 2026)
6
19
 
7
20
  * Dropped Ruby 3.x and Rails <8.1 support ([#45](https://github.com/hausgold/factory_bot_instrumentation/pull/45))
data/Makefile CHANGED
@@ -28,6 +28,7 @@ HEAD ?= head
28
28
  ID ?= id
29
29
  MKDIR ?= mkdir
30
30
  RM ?= rm
31
+ SED ?= sed
31
32
  SORT ?= sort
32
33
  TEST ?= test
33
34
  XARGS ?= xargs
@@ -174,6 +175,11 @@ stats:
174
175
  # Print all the notes from the code
175
176
  @$(call run-shell,$(BUNDLE) exec $(RAKE) stats)
176
177
 
177
- release:
178
+ build:
179
+ # Build and prepare the gem for releasing
180
+ @$(call run-shell,$(BUNDLE) exec $(RAKE) bundle_assets)
181
+ @$(SED) -i '/spec.extensions/d' factory_bot_instrumentation.gemspec
182
+
183
+ release: build
178
184
  # Release a new gem version
179
185
  @$(BUNDLE) exec $(RAKE) release
data/Rakefile CHANGED
@@ -11,6 +11,17 @@ require 'countless/rake_tasks'
11
11
  APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
12
12
  load 'rails/tasks/engine.rake'
13
13
 
14
+ desc 'Bundle the engine JavaScript and CSS sources into application.{js,css}'
15
+ task :bundle_assets do
16
+ require 'factory_bot/instrumentation/asset_bundler'
17
+ FactoryBot::Instrumentation::AssetBundler.bundle_all!.each do |kind, files|
18
+ conf = FactoryBot::Instrumentation::AssetBundler.config(kind)
19
+ rel = files.map { |f| f.sub("#{conf[:source_dir]}/", '') }
20
+ puts "Bundled #{files.size} #{kind.upcase} source(s) into " \
21
+ "#{conf[:output_name]}: #{rel.join(', ')}"
22
+ end
23
+ end
24
+
14
25
  desc 'Run all specs in spec directory (excluding plugin specs)'
15
26
  RSpec::Core::RakeTask.new(spec: [
16
27
  'db:drop', 'db:create', 'db:migrate', 'db:setup'
@@ -18,6 +29,14 @@ RSpec::Core::RakeTask.new(spec: [
18
29
 
19
30
  task default: :spec
20
31
 
32
+ # Neuter Bundler's `release:guard_clean` check. The `make build` step prunes
33
+ # the unbundled JS/CSS sources from `app/assets/**/factory_bot_instrumentation/`
34
+ # after bundling them into `application.{js,css}`, which leaves the working
35
+ # tree dirty by design. The commit + tag are already in place before release,
36
+ # so the clean-tree guard would only block pushing the artefact we just built.
37
+ Rake::Task['release:guard_clean'].clear
38
+ task 'release:guard_clean'
39
+
21
40
  # Configure all code statistics directories
22
41
  Countless.configure do |config|
23
42
  config.stats_base_directories = [
@@ -1,16 +1,458 @@
1
- // This is a manifest file that'll be compiled into application.js, which will
2
- // include all the files listed below.
1
+ // !!! AUTO-GENERATED FILE - DO NOT EDIT !!!
3
2
  //
4
- // Any JavaScript/Coffee file within this directory, lib/assets/javascripts,
5
- // vendor/assets/javascripts, or any plugin's vendor/assets/javascripts
6
- // directory can be referenced here using a relative path.
3
+ // This file bundles every JS source under
4
+ // +app/assets/javascripts/factory_bot_instrumentation/+ into a single asset. It is regenerated:
5
+ // * on `gem install` / `bundle install` (via a RubyGems extension
6
+ // hook), and
7
+ // * during gem release (via the `bundle_assets` Rake task).
7
8
  //
8
- // It's not advisable to add code directly here, but if you do, it'll appear at
9
- // the bottom of the compiled file. JavaScript code in this file should be
10
- // added after the last require_* statement.
9
+ // To rebuild it manually, run:
11
10
  //
12
- // Read Sprockets README
13
- // (https://github.com/rails/sprockets#sprockets-directives) for details about
14
- // supported directives.
11
+ // $ bundle exec rake bundle_assets
12
+
13
+ // >>> create.js
14
+ window.CreateForm = CreateForm = function()
15
+ {
16
+ this.scope = '#generate';
17
+ this.form = new Form(this.scope);
18
+ this.scenarios = window.scenarios;
19
+ this.select = $(`${this.scope} .scenario`);
20
+ this.desc = $(`${this.scope} .description`);
21
+
22
+ let self = this;
23
+
24
+ this.form.errorContent = function(payload, output, cb)
25
+ {
26
+ window.utils.waterfallWithHooks({
27
+ data: {
28
+ alert: `An unexpected error occurred. Looks like something went wrong
29
+ while generating your new entity. This might be a bug, or an
30
+ unexpected feature. It could be a temporary issue. When this
31
+ is persistent contact your friendly API Instrumentation
32
+ administrator.`,
33
+ output: output,
34
+ payload: payload,
35
+ pre: '',
36
+ post: ''
37
+ },
38
+ pre: window.hooks.preCreateError,
39
+ post: window.hooks.postCreateError,
40
+ action: (payload, innerCb) => {
41
+ cb(null, `
42
+ ${payload.pre}
43
+ <div class="alert alert-danger" role="alert">${payload.alert}</div>
44
+ <pre id="data">${payload.output}</pre>
45
+ ${window.utils.clipboardButton()}
46
+ ${payload.post}
47
+ `);
48
+ innerCb(null, payload);
49
+ }
50
+ });
51
+ };
52
+
53
+ this.form.resultContent = function(payload, output, cb)
54
+ {
55
+ let card = window.utils.card({
56
+ body: `
57
+ <pre id="data">${output}</pre>
58
+ ${window.utils.clipboardButton()}
59
+ `
60
+ });
61
+
62
+ window.utils.waterfallWithHooks({
63
+ data: {
64
+ alert: `A new ${self.scenario().name.toLowerCase()} was created.`,
65
+ output: output,
66
+ payload: payload,
67
+ cards: [card],
68
+ pre: '',
69
+ post: '',
70
+ openCard: '#details'
71
+ },
72
+ pre: window.hooks.preCreateResult,
73
+ post: window.hooks.postCreateResult,
74
+ action: (payload, innerCb) => {
75
+ cb(null, `
76
+ ${payload.pre}
77
+ <div class="alert alert-success" role="alert">${payload.alert}</div>
78
+ <div class="accordion" id="response">
79
+ ${payload.cards.join(' ')}
80
+ </div>
81
+ ${payload.post}
82
+ `);
83
+ innerCb(null, payload);
84
+ if (payload.openCard) {
85
+ $(
86
+ `.accordion#response button[data-target="${payload.openCard}"]`
87
+ ).click();
88
+ }
89
+ }
90
+ });
91
+ };
92
+ };
93
+
94
+ CreateForm.prototype.updateDesc = function()
95
+ {
96
+ this.desc.html(this.scenario().desc);
97
+ };
98
+
99
+ CreateForm.prototype.activeScenario = function()
100
+ {
101
+ raw = this.select.find(':selected').val().split('/');
102
+ return { group: raw[0], index: raw[1] };
103
+ };
104
+
105
+ CreateForm.prototype.scenario = function()
106
+ {
107
+ scenario = this.activeScenario();
108
+ return this.scenarios[scenario.group][scenario.index];
109
+ };
110
+
111
+ CreateForm.prototype.bind = function()
112
+ {
113
+ this.form.bind((event) => {
114
+ this.submit();
115
+ });
116
+
117
+ this.select.on('change', this.updateDesc.bind(this));
118
+ this.updateDesc();
119
+ };
120
+
121
+ CreateForm.prototype.submit = function()
122
+ {
123
+ let form = this.form;
124
+ let conf = this.scenario();
125
+
126
+ window.utils.waterfallWithHooks({
127
+ data: {
128
+ factory: conf.factory,
129
+ traits: conf.traits,
130
+ overwrite: conf.overwrite
131
+ },
132
+ pre: window.hooks.preCreate,
133
+ post: window.hooks.postCreate,
134
+ action: (payload, cb) => {
135
+ window.utils.request({
136
+ url: window.createUrl,
137
+ data: JSON.stringify(payload)
138
+ }, (err, result) => {
139
+ if (err) { return cb && cb(err); }
140
+ cb(null, { request: payload, response: result });
141
+ });
142
+ }
143
+ }, function(err, result) {
144
+ if (err) { return form.showError(err, err.responseText); }
145
+ form.showResult(result, result.response);
146
+ });
147
+ };
148
+
149
+ // >>> hooks.js
150
+ // You can define some custom hooks to enhance the functionality. With the help
151
+ // of the following hooks you are able to customize the outputs, perform
152
+ // additional HTTP requests or anything you like.
15
153
  //
16
- //= require_tree .
154
+ // All the hooks are designed to passthrough a payload. They receive this
155
+ // payload as the first argument, and a callback function to signal the end of
156
+ // the hook. You MUST pass the payload as second parameter to the callback, or
157
+ // pass an error object as first argument. You can modify the payload as you
158
+ // wish, eg. adding some data from subsequent requests.
159
+ //
160
+ // Example hooks:
161
+ //
162
+ // // Error case
163
+ // window.hooks.postCreate.push((payload, cb) => {
164
+ // cb({ error: true});
165
+ // });
166
+ //
167
+ // // Happy case
168
+ // window.hooks.postCreate.push((payload, cb) => {
169
+ // cb(null, Object.assign(payload, { additional: { data: true } }));
170
+ // });
171
+ //
172
+ // Mind the fact that you can define multiple custom functions per hook type.
173
+ // They are executed after each other in a waterfall like flow. The order of
174
+ // the hooks array is therefore essential.
175
+ window.hooks = {
176
+ // With the help of the +perCreate+ hooks you can manipulate the create
177
+ // request parameters. Think of an additional handling which reads an
178
+ // overwrite form or a kind of trait checkboxes to customize the factory
179
+ // call. The +payload+ looks like this:
180
+ //
181
+ // {
182
+ // factory: 'user',
183
+ // traits: ['confirmed'],
184
+ // overwrite: { password: 'secret' }
185
+ // }
186
+ preCreate: [],
187
+
188
+ // The +postCreate+ hook allows you to perform subsequent requests to fetch
189
+ // additional data. Think of a user instrumentation where you want to request
190
+ // a one time token for this user. This token can be added to the payload and
191
+ // can be shown with the help of the +preCreateResult+ hook. The payload
192
+ // contains the request parameters and the response body from the
193
+ // instrumentation request. Here comes an example +payload+:
194
+ //
195
+ // {
196
+ // request: { factory: 'user', /* [..] */ },
197
+ // response: { /* [..] */ }
198
+ // }
199
+ postCreate: [],
200
+
201
+ // With the help of the +preCreateResult+ hook you can customize the output
202
+ // of the result. You could also perform some subsequent requests or some UI
203
+ // preparations. You can access the output options and the runtime payload
204
+ // with all its data and make modifications to them. This hook is triggered
205
+ // before the result is rendered. A sample payload comes here:
206
+ //
207
+ // {
208
+ // alert: 'Your alert text.',
209
+ // output: 'Formatted response',
210
+ // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
211
+ // cards: [
212
+ // `The details accordion card,
213
+ // you can add more, remove the details card
214
+ // or reorder them`
215
+ // ],
216
+ // openCard: '#details', // Open a custom card, or none
217
+ // pre: 'Additinal HTML content before the alert.',
218
+ // post: 'Additinal HTML content after the formatted response output.'
219
+ // }
220
+ preCreateResult: [],
221
+
222
+ // In case you want to perform some logic after the result is rendered, you
223
+ // can use the +postCreateResult+ hook. You can access the output options and
224
+ // the runtime payload with all its data, but changes to them won't take
225
+ // effect. The +payload+ looks like this:
226
+ //
227
+ // {
228
+ // alert: 'Your alert text.',
229
+ // output: 'Formatted response',
230
+ // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
231
+ // cards: [
232
+ // `The details accordion card,
233
+ // you can add more, remove the details card
234
+ // or reorder them`
235
+ // ],
236
+ // openCard: '#details', // Open a custom card, or none
237
+ // pre: 'Additinal HTML content before the alert.',
238
+ // post: 'Additinal HTML content after the formatted response output.'
239
+ // }
240
+ postCreateResult: [],
241
+
242
+ // With the help of the +preCreateError+ hook you can customize the output of
243
+ // the error. Furthermore you can perform some subsequent requests or
244
+ // whatever comes to your mind. You can access the output options and the
245
+ // runtime payload with all its data and make modifications to them. This
246
+ // hook is triggered before the error is rendered. A sample payload comes
247
+ // here:
248
+ //
249
+ // {
250
+ // alert: 'Your alert text.',
251
+ // output: 'Formatted response',
252
+ // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
253
+ // pre: 'Additinal HTML content before the alert.',
254
+ // post: 'Additinal HTML content after the formatted response output.'
255
+ // }
256
+ preCreateError: [],
257
+
258
+ // In case you want to perform some magic after an error occurred, you can use
259
+ // the +postCreateError+ hook. You can access the output options and the
260
+ // runtime payload with all its data, but changes to them won't take effect
261
+ // because this hook is triggered after the error is rendered. The +payload+
262
+ // looks like this:
263
+ //
264
+ // {
265
+ // alert: 'Your alert text.',
266
+ // output: 'Formatted response',
267
+ // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
268
+ // pre: 'Additinal HTML content before the alert.',
269
+ // post: 'Additinal HTML content after the formatted response output.'
270
+ // }
271
+ postCreateError: []
272
+ };
273
+
274
+ // >>> lib/form.js
275
+ window.Form = Form = function(scope)
276
+ {
277
+ this.button = $(scope).find('button');
278
+ this.result = $('#result');
279
+ this.spinner = $('#spinner');
280
+ };
281
+
282
+ Form.prototype.bind = function(action)
283
+ {
284
+ this.button.on('click', (event) => {
285
+ event.preventDefault();
286
+ this.hideResult();
287
+ action(event);
288
+ return false;
289
+ });
290
+ };
291
+
292
+ Form.prototype.hideResult = function()
293
+ {
294
+ this.result.hide();
295
+ this.button.prop('disabled', true);
296
+ this.spinner.show();
297
+ };
298
+
299
+ Form.prototype.showResultContainer = function(html)
300
+ {
301
+ this.result.html(html);
302
+ $('pre').each((i, block) => hljs.highlightBlock(block));
303
+ new ClipboardJS('.cb-copy');
304
+ this.result.show();
305
+ this.button.prop('disabled', false);
306
+ };
307
+
308
+ Form.prototype.showError = function(payload, output)
309
+ {
310
+ this.spinner.hide();
311
+ output = window.utils.prepareOutput(output);
312
+ this.errorContent(payload, output, (err, html) => {
313
+ this.showResultContainer(html);
314
+ });
315
+ };
316
+
317
+ Form.prototype.errorContent = function(payload, output, cb)
318
+ {
319
+ cb(null, `
320
+ <pre id="data">${output}</pre>
321
+ ${window.utils.clipboardButton()}
322
+ `);
323
+ };
324
+
325
+ Form.prototype.showResult = function(payload, output)
326
+ {
327
+ this.spinner.hide();
328
+ output = window.utils.prepareOutput(output);
329
+ this.resultContent(payload, output, (err, html) => {
330
+ this.showResultContainer(html);
331
+ });
332
+ };
333
+
334
+ Form.prototype.resultContent = function(payload, output, cb)
335
+ {
336
+ cb(null, `
337
+ <pre id="data">${output}</pre>
338
+ ${window.utils.clipboardButton()}
339
+ `);
340
+ };
341
+
342
+ // >>> lib/utils.js
343
+ window.utils = Utils = {};
344
+
345
+ Utils.pushWaterfallPayload = function(data)
346
+ {
347
+ return (cb) => cb(null, data);
348
+ };
349
+
350
+ Utils.waterfallWithHooks = function(opts, cb)
351
+ {
352
+ cb = cb || function(){};
353
+ opts = Object.assign({
354
+ pre: [],
355
+ post: [],
356
+ data: {},
357
+ action: (payload, cb) => cb(null, payload)
358
+ }, opts);
359
+
360
+ async.waterfall([
361
+ // Yield the data to pre hooks
362
+ Utils.pushWaterfallPayload(opts.data),
363
+ // Perform pre hooks
364
+ ...opts.pre,
365
+ // Perform the create request
366
+ opts.action,
367
+ // Perform post hooks
368
+ ...opts.post
369
+ ], cb);
370
+ };
371
+
372
+ Utils.request = function(opts, cb)
373
+ {
374
+ opts = Object.assign({
375
+ url: '/',
376
+ type: 'POST',
377
+ data: '{}',
378
+ dataType: 'json',
379
+ contentType: 'application/json; charset=utf-8',
380
+ ignoreErrors: false
381
+ }, opts || {}, {
382
+ success: (result) => cb(null, result)
383
+ });
384
+
385
+ errCb = (err) => cb(err);
386
+ if (opts.ignoreErrors) {
387
+ errCb = (err) => cb(null, err);
388
+ }
389
+
390
+ $.ajax(opts).fail(errCb);
391
+ };
392
+
393
+ Utils.escape = function(str)
394
+ {
395
+ return str.replace(/&/g, "&amp;")
396
+ .replace(/</g, "&lt;")
397
+ .replace(/>/g, "&gt;");
398
+ };
399
+
400
+ Utils.prepareOutput = function(output)
401
+ {
402
+ try {
403
+ if (typeof output !== 'object') { output = JSON.parse(output); }
404
+ output = JSON.stringify(output, null, 2);
405
+ } catch { }
406
+
407
+ return window.utils.escape(output);
408
+ };
409
+
410
+ Utils.clipboardButton = function(id)
411
+ {
412
+ id = id || 'data';
413
+ return `
414
+ <span class="btn btn-primary cb-copy"
415
+ data-clipboard-target="#${id}">
416
+ <i class="fas fa-paste"></i> Copy result to clipboard
417
+ </span>
418
+ `;
419
+ };
420
+
421
+ Utils.clipboardBadge = function(id)
422
+ {
423
+ id = id || 'data';
424
+ return `
425
+ <span class="badge badge-dark cb-copy" title="Copy result to clipboard"
426
+ data-clipboard-target="#${id}"><i class="fas fa-paste"></i>
427
+ </span>
428
+ `;
429
+ };
430
+
431
+ Utils.card = function(opts)
432
+ {
433
+ opts = Object.assign({
434
+ id: 'details',
435
+ icon: 'fa-asterisk',
436
+ title: 'Details',
437
+ body: ''
438
+ }, opts || {});
439
+
440
+ return `
441
+ <div class="card">
442
+ <div class="card-header">
443
+ <h5 class="mb-0">
444
+ <button class="btn btn-link collapsed" type="button"
445
+ data-toggle="collapse" data-target="#${opts.id}"
446
+ aria-expanded="false">
447
+ <i class="fas ${opts.icon}"></i> ${opts.title}</h5>
448
+ </button>
449
+ </h5>
450
+ </div>
451
+ <div id="${opts.id}" class="collapse" data-parent="#response">
452
+ <div class="card-body">
453
+ ${opts.body}
454
+ </div>
455
+ </div>
456
+ </div>
457
+ `;
458
+ };
@@ -1,42 +1,18 @@
1
1
  /*
2
- * This is a manifest file that'll be compiled into application.css, which will
3
- * include all the files listed below.
2
+ * !!! AUTO-GENERATED FILE - DO NOT EDIT !!!
4
3
  *
5
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets,
6
- * vendor/assets/stylesheets, or any plugin's vendor/assets/stylesheets
7
- * directory can be referenced here using a relative path.
4
+ * This file bundles every CSS source under
5
+ * +app/assets/stylesheets/factory_bot_instrumentation/+ into a single asset. It is regenerated:
6
+ * * on `gem install` / `bundle install` (via a RubyGems extension
7
+ * hook), and
8
+ * * during gem release (via the `bundle_assets` Rake task).
8
9
  *
9
- * You're free to add application-wide styles to this file and they'll appear
10
- * at the bottom of the compiled file so the styles you add here take
11
- * precedence over styles defined in any other CSS/SCSS files in this
12
- * directory. Styles in this file should be added after the last require_*
13
- * statement. It is generally better to create a new file per style scope.
10
+ * To rebuild it manually, run:
14
11
  *
15
- *= require_tree .
16
- *= require_self
12
+ * $ bundle exec rake bundle_assets
17
13
  */
18
14
 
19
- .row { margin-top: 5em }
20
- .result-container { background-color: #f8f8f8 }
21
- .hljs { background-color: #fff; }
22
- .jumbotron { padding: 1rem }
23
- .jumbotron .row { margin-top: 0 }
24
- .badge { cursor: pointer }
25
- .btn-link:hover, .btn-link:active, .btn-link:focus { text-decoration: none }
26
-
27
- pre {
28
- margin-bottom: 0;
29
- white-space: pre-wrap;
30
- white-space: -moz-pre-wrap;
31
- white-space: -pre-wrap;
32
- white-space: -o-pre-wrap;
33
- word-wrap: break-word;
34
- }
35
-
36
- pre + .btn {
37
- margin-top: 1rem;
38
- }
39
-
15
+ /* >>> spinner.css */
40
16
  .spinner {
41
17
  margin: 100px auto;
42
18
  width: 50px;
@@ -89,3 +65,25 @@ pre + .btn {
89
65
  -webkit-transform: scaleY(1.0);
90
66
  }
91
67
  }
68
+
69
+ /* >>> utils.css */
70
+ .row { margin-top: 5em }
71
+ .result-container { background-color: #f8f8f8 }
72
+ .hljs { background-color: #fff; }
73
+ .jumbotron { padding: 1rem }
74
+ .jumbotron .row { margin-top: 0 }
75
+ .badge { cursor: pointer }
76
+ .btn-link:hover, .btn-link:active, .btn-link:focus { text-decoration: none }
77
+
78
+ pre {
79
+ margin-bottom: 0;
80
+ white-space: pre-wrap;
81
+ white-space: -moz-pre-wrap;
82
+ white-space: -pre-wrap;
83
+ white-space: -o-pre-wrap;
84
+ word-wrap: break-word;
85
+ }
86
+
87
+ pre + .btn {
88
+ margin-top: 1rem;
89
+ }
@@ -24,13 +24,20 @@ Gem::Specification.new do |spec|
24
24
  }
25
25
 
26
26
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
- f.match(%r{^(test|spec|features)/})
27
+ next false if f.match?(/application\.(js|css)/)
28
+
29
+ f.match(%r{^(test|spec|features|ext)/}) || f.match(/\.(js|css)$/)
28
30
  end
29
31
 
30
32
  spec.bindir = 'exe'
31
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
34
  spec.require_paths = ['lib']
33
35
 
36
+ # Regenerate the bundled JavaScript asset whenever the gem is
37
+ # installed. This is the standard RubyGems / Bundler hook and fires
38
+ # for plain +gem install+ as well as for Git-source +bundle install+
39
+ # in third-party applications.
40
+
34
41
  spec.required_ruby_version = '>= 4.0'
35
42
 
36
43
  spec.add_dependency 'factory_bot', '~> 6.2'