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.
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FactoryBot
4
+ module Instrumentation
5
+ # A tiny, dependency-free replacement for Sprockets' +require_tree+
6
+ # directive. It concatenates the JavaScript or CSS sources shipped
7
+ # with this engine into a single +application.{js,css}+ asset so
8
+ # that the engine no longer needs an asset pipeline at runtime.
9
+ #
10
+ # The bundles are regenerated in two situations:
11
+ #
12
+ # * During gem release, via the +bundle_assets+ Rake task which is
13
+ # hooked into +rake build+.
14
+ # * On +gem install+ / +bundle install+ (including Git source installs
15
+ # in third-party applications), via the RubyGems extension shim at
16
+ # +ext/factory_bot_instrumentation/extconf.rb+.
17
+ module AssetBundler
18
+ module_function
19
+
20
+ # Absolute path to the gem root (where the +app+ and +lib+ folders
21
+ # live), derived from this file's location.
22
+ ROOT_DIR = File.expand_path('../../..', __dir__)
23
+
24
+ # Per-kind configuration. Each entry describes where the sources
25
+ # live, which extension to bundle, where to write the manifest and
26
+ # how to format the comments wrapping each chunk.
27
+ KINDS = {
28
+ js: {
29
+ source_dir: File.join(
30
+ ROOT_DIR, 'app', 'assets', 'javascripts',
31
+ 'factory_bot_instrumentation'
32
+ ),
33
+ output_name: 'application.js',
34
+ extension: 'js',
35
+ line_comment: '// %s',
36
+ banner_format: :line
37
+ },
38
+ css: {
39
+ source_dir: File.join(
40
+ ROOT_DIR, 'app', 'assets', 'stylesheets',
41
+ 'factory_bot_instrumentation'
42
+ ),
43
+ output_name: 'application.css',
44
+ extension: 'css',
45
+ line_comment: '/* %s */',
46
+ banner_format: :block
47
+ }
48
+ }.freeze
49
+
50
+ # Plain-text banner content, formatted per kind by +banner+. The
51
+ # +%<kind>s+ and +%<source_rel>s+ placeholders are interpolated at
52
+ # render time via +format+.
53
+ BANNER_TEMPLATE = <<~TEXT
54
+ !!! AUTO-GENERATED FILE - DO NOT EDIT !!!
55
+
56
+ This file bundles every %<kind>s source under
57
+ +%<source_rel>s/+ into a single asset. It is regenerated:
58
+ * on `gem install` / `bundle install` (via a RubyGems extension
59
+ hook), and
60
+ * during gem release (via the `bundle_assets` Rake task).
61
+
62
+ To rebuild it manually, run:
63
+
64
+ $ bundle exec rake bundle_assets
65
+ TEXT
66
+
67
+ # All asset kinds known to the bundler.
68
+ #
69
+ # @return [Array<Symbol>] the registered kind identifiers
70
+ def kinds
71
+ KINDS.keys
72
+ end
73
+
74
+ # Build the bundle for the given +kind+ and write it to the
75
+ # corresponding manifest. The manifest itself is excluded from the
76
+ # input set so that re-runs are idempotent.
77
+ #
78
+ # @param kind [Symbol] one of +:js+ or +:css+
79
+ # @return [Array<String>] absolute paths of the source files that
80
+ # were concatenated, in the order they appear in the output
81
+ # @raise [ArgumentError] if +kind+ is not a known asset kind
82
+ def bundle!(kind)
83
+ conf = config(kind)
84
+ files = sources(kind)
85
+ File.write(
86
+ File.join(conf[:source_dir], conf[:output_name]),
87
+ banner(kind) + files.map { |f| chunk(kind, f) }.join
88
+ )
89
+ files
90
+ end
91
+
92
+ # Build every known asset kind in turn.
93
+ #
94
+ # @return [Hash{Symbol => Array<String>}] a mapping of kind to the
95
+ # absolute paths of the source files that were concatenated for
96
+ # that kind
97
+ #
98
+ # rubocop:disable Rails/IndexWith -- because we're
99
+ # dependency-free in here
100
+ def bundle_all!
101
+ kinds.to_h { |kind| [kind, bundle!(kind)] }
102
+ end
103
+ # rubocop:enable Rails/IndexWith
104
+
105
+ # All sources to include for +kind+, sorted so the result is
106
+ # stable across platforms and filesystems. The manifest itself is
107
+ # excluded to avoid recursive inclusion.
108
+ #
109
+ # @param kind [Symbol] one of +:js+ or +:css+
110
+ # @return [Array<String>] absolute paths of the matching source
111
+ # files, sorted ascending
112
+ # @raise [ArgumentError] if +kind+ is not a known asset kind
113
+ def sources(kind)
114
+ conf = config(kind)
115
+ output = File.expand_path(File.join(conf[:source_dir],
116
+ conf[:output_name]))
117
+ Dir.glob(File.join(conf[:source_dir], '**', "*.#{conf[:extension]}"))
118
+ .reject { |path| File.expand_path(path) == output }
119
+ .sort
120
+ end
121
+
122
+ # Look up the configuration for +kind+, raising on unknown values.
123
+ #
124
+ # @param kind [Symbol] the kind identifier to look up
125
+ # @return [Hash] the configuration entry from +KINDS+, with keys
126
+ # +:source_dir+, +:output_name+, +:extension+, +:line_comment+
127
+ # and +:banner_format+
128
+ # @raise [ArgumentError] if +kind+ is not a known asset kind
129
+ def config(kind)
130
+ KINDS.fetch(kind) do
131
+ raise ArgumentError,
132
+ "Unknown asset kind #{kind.inspect}; expected one of " \
133
+ "#{KINDS.keys.inspect}"
134
+ end
135
+ end
136
+
137
+ # Path of +file+ relative to its kind's source directory, used in
138
+ # the per-chunk comment marker so the bundled output remains
139
+ # debuggable.
140
+ #
141
+ # @param kind [Symbol] one of +:js+ or +:css+
142
+ # @param file [String] the absolute path of a source file
143
+ # @return [String] +file+ stripped of the kind's source directory
144
+ # prefix
145
+ def relative_source(kind, file)
146
+ file.sub("#{config(kind)[:source_dir]}/", '')
147
+ end
148
+
149
+ # Wrap a single source file with a comment that names its relative
150
+ # path. The returned snippet starts with a newline so that chunks
151
+ # concatenate cleanly under the banner.
152
+ #
153
+ # @param kind [Symbol] one of +:js+ or +:css+
154
+ # @param path [String] the absolute path of the source file to
155
+ # wrap
156
+ # @return [String] the comment-wrapped source contents
157
+ def chunk(kind, path)
158
+ conf = config(kind)
159
+ body = File.read(path)
160
+ body += "\n" unless body.end_with?("\n")
161
+ marker = format(conf[:line_comment],
162
+ ">>> #{relative_source(kind, path)}")
163
+ "\n#{marker}\n#{body}"
164
+ end
165
+
166
+ # Render the auto-generation banner in the comment style of +kind+.
167
+ # JavaScript bundles get line comments (+// ...+), CSS bundles get
168
+ # a single block comment (+/* ... */+).
169
+ #
170
+ # @param kind [Symbol] one of +:js+ or +:css+
171
+ # @return [String] the banner ready to be prepended to the bundle,
172
+ # including the trailing newline
173
+ def banner(kind)
174
+ conf = config(kind)
175
+ text = format(
176
+ BANNER_TEMPLATE,
177
+ kind: conf[:extension].upcase,
178
+ source_rel: conf[:source_dir].sub("#{ROOT_DIR}/", '')
179
+ )
180
+
181
+ case conf[:banner_format]
182
+ when :line
183
+ lines = text.each_line
184
+ .map { |l| format(conf[:line_comment], l.chomp).rstrip }
185
+ .join("\n")
186
+ "#{lines}\n"
187
+ when :block
188
+ body = text.each_line.map { |l| " * #{l.chomp}".rstrip }.join("\n")
189
+ "/*\n#{body}\n */\n"
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -38,6 +38,30 @@ module FactoryBot
38
38
  paths['app/views'].push(
39
39
  app_path.join('views/factory_bot/instrumentation').to_s
40
40
  )
41
+
42
+ # Register the engine's assets with the host application's Sprockets
43
+ # precompile list so they are picked up by +rake assets:precompile+.
44
+ # The Sprockets 4+ syntax expects bare logical paths, while older
45
+ # versions need a +proc+ matcher - we handle both.
46
+ initializer 'factory_bot_instrumentation.assets', group: :all do |app|
47
+ if app.config.respond_to?(:assets) && defined?(Sprockets)
48
+ sprockets_4_or_later =
49
+ Gem::Version.new(Sprockets::VERSION) >= Gem::Version.new('4')
50
+
51
+ %w[
52
+ factory_bot_instrumentation/application.js
53
+ factory_bot_instrumentation/application.css
54
+ ].each do |asset_path|
55
+ app.config.assets.precompile << (
56
+ if sprockets_4_or_later
57
+ asset_path
58
+ else
59
+ proc { |path| path == asset_path }
60
+ end
61
+ )
62
+ end
63
+ end
64
+ end
41
65
  end
42
66
  end
43
67
  end
@@ -4,7 +4,7 @@ module FactoryBot
4
4
  # The gem version details.
5
5
  module Instrumentation
6
6
  # The version of the +factory_bot_instrumentation+ gem
7
- VERSION = '2.9.0'
7
+ VERSION = '3.0.0'
8
8
 
9
9
  class << self
10
10
  # Returns the version of gem as a string.
@@ -19,6 +19,9 @@ module FactoryBot
19
19
  # Setup a Zeitwerk autoloader instance and configure it
20
20
  loader = Zeitwerk::Loader.for_gem_extension(FactoryBot)
21
21
 
22
+ # Do not auto load some parts of the gem
23
+ loader.ignore("#{__dir__}/instrumentation/asset_bundler.rb")
24
+
22
25
  # Finish the auto loader configuration
23
26
  loader.setup
24
27
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factory_bot_instrumentation
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hermann Mayer
@@ -105,12 +105,7 @@ files:
105
105
  - Makefile
106
106
  - README.md
107
107
  - Rakefile
108
- - app/assets/config/factory_bot_instrumentation_manifest.js
109
108
  - app/assets/javascripts/factory_bot_instrumentation/application.js
110
- - app/assets/javascripts/factory_bot_instrumentation/create.js
111
- - app/assets/javascripts/factory_bot_instrumentation/hooks.js
112
- - app/assets/javascripts/factory_bot_instrumentation/lib/form.js
113
- - app/assets/javascripts/factory_bot_instrumentation/lib/utils.js
114
109
  - app/assets/stylesheets/factory_bot_instrumentation/application.css
115
110
  - app/controllers/factory_bot/instrumentation/application_controller.rb
116
111
  - app/controllers/factory_bot/instrumentation/root_controller.rb
@@ -139,6 +134,7 @@ files:
139
134
  - factory_bot_instrumentation.gemspec
140
135
  - gemfiles/rails_8.1.gemfile
141
136
  - lib/factory_bot/instrumentation.rb
137
+ - lib/factory_bot/instrumentation/asset_bundler.rb
142
138
  - lib/factory_bot/instrumentation/configuration.rb
143
139
  - lib/factory_bot/instrumentation/engine.rb
144
140
  - lib/factory_bot/instrumentation/version.rb
@@ -1,2 +0,0 @@
1
- //= link_directory ../javascripts/factory_bot_instrumentation .js
2
- //= link_directory ../stylesheets/factory_bot_instrumentation .css
@@ -1,134 +0,0 @@
1
- window.CreateForm = CreateForm = function()
2
- {
3
- this.scope = '#generate';
4
- this.form = new Form(this.scope);
5
- this.scenarios = window.scenarios;
6
- this.select = $(`${this.scope} .scenario`);
7
- this.desc = $(`${this.scope} .description`);
8
-
9
- let self = this;
10
-
11
- this.form.errorContent = function(payload, output, cb)
12
- {
13
- window.utils.waterfallWithHooks({
14
- data: {
15
- alert: `An unexpected error occurred. Looks like something went wrong
16
- while generating your new entity. This might be a bug, or an
17
- unexpected feature. It could be a temporary issue. When this
18
- is persistent contact your friendly API Instrumentation
19
- administrator.`,
20
- output: output,
21
- payload: payload,
22
- pre: '',
23
- post: ''
24
- },
25
- pre: window.hooks.preCreateError,
26
- post: window.hooks.postCreateError,
27
- action: (payload, innerCb) => {
28
- cb(null, `
29
- ${payload.pre}
30
- <div class="alert alert-danger" role="alert">${payload.alert}</div>
31
- <pre id="data">${payload.output}</pre>
32
- ${window.utils.clipboardButton()}
33
- ${payload.post}
34
- `);
35
- innerCb(null, payload);
36
- }
37
- });
38
- };
39
-
40
- this.form.resultContent = function(payload, output, cb)
41
- {
42
- let card = window.utils.card({
43
- body: `
44
- <pre id="data">${output}</pre>
45
- ${window.utils.clipboardButton()}
46
- `
47
- });
48
-
49
- window.utils.waterfallWithHooks({
50
- data: {
51
- alert: `A new ${self.scenario().name.toLowerCase()} was created.`,
52
- output: output,
53
- payload: payload,
54
- cards: [card],
55
- pre: '',
56
- post: '',
57
- openCard: '#details'
58
- },
59
- pre: window.hooks.preCreateResult,
60
- post: window.hooks.postCreateResult,
61
- action: (payload, innerCb) => {
62
- cb(null, `
63
- ${payload.pre}
64
- <div class="alert alert-success" role="alert">${payload.alert}</div>
65
- <div class="accordion" id="response">
66
- ${payload.cards.join(' ')}
67
- </div>
68
- ${payload.post}
69
- `);
70
- innerCb(null, payload);
71
- if (payload.openCard) {
72
- $(
73
- `.accordion#response button[data-target="${payload.openCard}"]`
74
- ).click();
75
- }
76
- }
77
- });
78
- };
79
- };
80
-
81
- CreateForm.prototype.updateDesc = function()
82
- {
83
- this.desc.html(this.scenario().desc);
84
- };
85
-
86
- CreateForm.prototype.activeScenario = function()
87
- {
88
- raw = this.select.find(':selected').val().split('/');
89
- return { group: raw[0], index: raw[1] };
90
- };
91
-
92
- CreateForm.prototype.scenario = function()
93
- {
94
- scenario = this.activeScenario();
95
- return this.scenarios[scenario.group][scenario.index];
96
- };
97
-
98
- CreateForm.prototype.bind = function()
99
- {
100
- this.form.bind((event) => {
101
- this.submit();
102
- });
103
-
104
- this.select.on('change', this.updateDesc.bind(this));
105
- this.updateDesc();
106
- };
107
-
108
- CreateForm.prototype.submit = function()
109
- {
110
- let form = this.form;
111
- let conf = this.scenario();
112
-
113
- window.utils.waterfallWithHooks({
114
- data: {
115
- factory: conf.factory,
116
- traits: conf.traits,
117
- overwrite: conf.overwrite
118
- },
119
- pre: window.hooks.preCreate,
120
- post: window.hooks.postCreate,
121
- action: (payload, cb) => {
122
- window.utils.request({
123
- url: window.createUrl,
124
- data: JSON.stringify(payload)
125
- }, (err, result) => {
126
- if (err) { return cb && cb(err); }
127
- cb(null, { request: payload, response: result });
128
- });
129
- }
130
- }, function(err, result) {
131
- if (err) { return form.showError(err, err.responseText); }
132
- form.showResult(result, result.response);
133
- });
134
- };
@@ -1,123 +0,0 @@
1
- // You can define some custom hooks to enhance the functionality. With the help
2
- // of the following hooks you are able to customize the outputs, perform
3
- // additional HTTP requests or anything you like.
4
- //
5
- // All the hooks are designed to passthrough a payload. They receive this
6
- // payload as the first argument, and a callback function to signal the end of
7
- // the hook. You MUST pass the payload as second parameter to the callback, or
8
- // pass an error object as first argument. You can modify the payload as you
9
- // wish, eg. adding some data from subsequent requests.
10
- //
11
- // Example hooks:
12
- //
13
- // // Error case
14
- // window.hooks.postCreate.push((payload, cb) => {
15
- // cb({ error: true});
16
- // });
17
- //
18
- // // Happy case
19
- // window.hooks.postCreate.push((payload, cb) => {
20
- // cb(null, Object.assign(payload, { additional: { data: true } }));
21
- // });
22
- //
23
- // Mind the fact that you can define multiple custom functions per hook type.
24
- // They are executed after each other in a waterfall like flow. The order of
25
- // the hooks array is therefore essential.
26
- window.hooks = {
27
- // With the help of the +perCreate+ hooks you can manipulate the create
28
- // request parameters. Think of an additional handling which reads an
29
- // overwrite form or a kind of trait checkboxes to customize the factory
30
- // call. The +payload+ looks like this:
31
- //
32
- // {
33
- // factory: 'user',
34
- // traits: ['confirmed'],
35
- // overwrite: { password: 'secret' }
36
- // }
37
- preCreate: [],
38
-
39
- // The +postCreate+ hook allows you to perform subsequent requests to fetch
40
- // additional data. Think of a user instrumentation where you want to request
41
- // a one time token for this user. This token can be added to the payload and
42
- // can be shown with the help of the +preCreateResult+ hook. The payload
43
- // contains the request parameters and the response body from the
44
- // instrumentation request. Here comes an example +payload+:
45
- //
46
- // {
47
- // request: { factory: 'user', /* [..] */ },
48
- // response: { /* [..] */ }
49
- // }
50
- postCreate: [],
51
-
52
- // With the help of the +preCreateResult+ hook you can customize the output
53
- // of the result. You could also perform some subsequent requests or some UI
54
- // preparations. You can access the output options and the runtime payload
55
- // with all its data and make modifications to them. This hook is triggered
56
- // before the result is rendered. A sample payload comes here:
57
- //
58
- // {
59
- // alert: 'Your alert text.',
60
- // output: 'Formatted response',
61
- // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
62
- // cards: [
63
- // `The details accordion card,
64
- // you can add more, remove the details card
65
- // or reorder them`
66
- // ],
67
- // openCard: '#details', // Open a custom card, or none
68
- // pre: 'Additinal HTML content before the alert.',
69
- // post: 'Additinal HTML content after the formatted response output.'
70
- // }
71
- preCreateResult: [],
72
-
73
- // In case you want to perform some logic after the result is rendered, you
74
- // can use the +postCreateResult+ hook. You can access the output options and
75
- // the runtime payload with all its data, but changes to them won't take
76
- // effect. The +payload+ looks like this:
77
- //
78
- // {
79
- // alert: 'Your alert text.',
80
- // output: 'Formatted response',
81
- // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
82
- // cards: [
83
- // `The details accordion card,
84
- // you can add more, remove the details card
85
- // or reorder them`
86
- // ],
87
- // openCard: '#details', // Open a custom card, or none
88
- // pre: 'Additinal HTML content before the alert.',
89
- // post: 'Additinal HTML content after the formatted response output.'
90
- // }
91
- postCreateResult: [],
92
-
93
- // With the help of the +preCreateError+ hook you can customize the output of
94
- // the error. Furthermore you can perform some subsequent requests or
95
- // whatever comes to your mind. You can access the output options and the
96
- // runtime payload with all its data and make modifications to them. This
97
- // hook is triggered before the error is rendered. A sample payload comes
98
- // here:
99
- //
100
- // {
101
- // alert: 'Your alert text.',
102
- // output: 'Formatted response',
103
- // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
104
- // pre: 'Additinal HTML content before the alert.',
105
- // post: 'Additinal HTML content after the formatted response output.'
106
- // }
107
- preCreateError: [],
108
-
109
- // In case you want to perform some magic after an error occurred, you can use
110
- // the +postCreateError+ hook. You can access the output options and the
111
- // runtime payload with all its data, but changes to them won't take effect
112
- // because this hook is triggered after the error is rendered. The +payload+
113
- // looks like this:
114
- //
115
- // {
116
- // alert: 'Your alert text.',
117
- // output: 'Formatted response',
118
- // payload: { request: { /* [..] */ }, response: { /* [..] */ } },
119
- // pre: 'Additinal HTML content before the alert.',
120
- // post: 'Additinal HTML content after the formatted response output.'
121
- // }
122
- postCreateError: []
123
- };
@@ -1,66 +0,0 @@
1
- window.Form = Form = function(scope)
2
- {
3
- this.button = $(scope).find('button');
4
- this.result = $('#result');
5
- this.spinner = $('#spinner');
6
- };
7
-
8
- Form.prototype.bind = function(action)
9
- {
10
- this.button.on('click', (event) => {
11
- event.preventDefault();
12
- this.hideResult();
13
- action(event);
14
- return false;
15
- });
16
- };
17
-
18
- Form.prototype.hideResult = function()
19
- {
20
- this.result.hide();
21
- this.button.prop('disabled', true);
22
- this.spinner.show();
23
- };
24
-
25
- Form.prototype.showResultContainer = function(html)
26
- {
27
- this.result.html(html);
28
- $('pre').each((i, block) => hljs.highlightBlock(block));
29
- new ClipboardJS('.cb-copy');
30
- this.result.show();
31
- this.button.prop('disabled', false);
32
- };
33
-
34
- Form.prototype.showError = function(payload, output)
35
- {
36
- this.spinner.hide();
37
- output = window.utils.prepareOutput(output);
38
- this.errorContent(payload, output, (err, html) => {
39
- this.showResultContainer(html);
40
- });
41
- };
42
-
43
- Form.prototype.errorContent = function(payload, output, cb)
44
- {
45
- cb(null, `
46
- <pre id="data">${output}</pre>
47
- ${window.utils.clipboardButton()}
48
- `);
49
- };
50
-
51
- Form.prototype.showResult = function(payload, output)
52
- {
53
- this.spinner.hide();
54
- output = window.utils.prepareOutput(output);
55
- this.resultContent(payload, output, (err, html) => {
56
- this.showResultContainer(html);
57
- });
58
- };
59
-
60
- Form.prototype.resultContent = function(payload, output, cb)
61
- {
62
- cb(null, `
63
- <pre id="data">${output}</pre>
64
- ${window.utils.clipboardButton()}
65
- `);
66
- };