factory_bot-blueprint-rspec 0.4.0 → 0.5.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: f99bbc7d1b7e6a8a7fb74d0fb64b88efb1baff08a583fdab7491db3c81ab4233
4
- data.tar.gz: 2aa5ba9a0705e6389236e8e5699e2d0cb7c418c70373b9ef74d71c854450a473
3
+ metadata.gz: 2559690c18cf146c86024386fff1630989c0e201499564fda52e09003a58b6bf
4
+ data.tar.gz: a6ecd312a009b3b3caf698fec6b8b1f2eb2f2973aaf3f8d8d9c92d5ab91e3b40
5
5
  SHA512:
6
- metadata.gz: 71b5c78c7b59fa4f97c2171591dbe2533fd901fc4e7bd7647e48e017ce26f509e9c4c75178d3d86494c275b92e07431f11ca88c66de0164cf9b77cf3c2c1e321
7
- data.tar.gz: d8e3182c44520b7ea3d2233388bf7711690f71c050f9a37089955ddad25cb9c62c5170440a486a977ae7d0b8c90a78001195bde7ea1e224026b7a497d43b67b5
6
+ metadata.gz: 4d38916bb4b3d7b7d68cd60fb73fc4b15acd5513e08e447d9a5c3907972c409a686367e43dc6af562c8343388aa52e7a0d2d5694382b92e57be91e6c9aed1a40
7
+ data.tar.gz: 29886e7b5454c8b346e0d6c6f0a961c1913079a9ed85acdbbe03b751e7ae6f8530f3a7bcf725775662bd8f9e7e5ac20bbd6bbcc18d0587106bd9e37f15d50796
@@ -4,129 +4,18 @@ module FactoryBot
4
4
  module Blueprint
5
5
  module RSpec
6
6
  # Helper methods to integrate <code>factory_bot-blueprint</code> with RSpec.
7
- # This module is automatically extended to {RSpec::Core::ExampleGroup}.
8
- #
9
- # To use FactoryBot::Blueprint from RSpec with minimal effort, usually {#letbp} is the best choice.
7
+ # This module is automatically extended to <code>RSpec::Core::ExampleGroup</code>.
10
8
  module Driver
11
- # Shorthand method for <code>let(name) { ::FactoryBot::Blueprint.plan(...) }</code>.
12
- # You can access the let binding context by <code>#ext</code> in DSL code.
13
- # @param name [Symbol] name of the object to be declared using RSpec's <code>let</code>
14
- # @param inherit [Boolean] whether to extend the blueprint by <code>super()</code>
15
- # @yield Write Blueprint DSL code here
16
- # @example
17
- # RSpec.describe "something" do
18
- # let(:blog_id) { SecureRandom.uuid }
19
- #
20
- # let_blueprint(:blog_bp) do
21
- # let.blog(id: ext.blog_id, title: "Daily log") do
22
- # let.article(title: "Article 1")
23
- # article(title: "Article 2")
24
- # article(title: "Article 3")
25
- # end
26
- # end
27
- # end
28
- def let_blueprint(name, inherit: false, &)
29
- let(name) { ::FactoryBot::Blueprint.plan(inherit ? super() : nil, ext: self, &) }
30
- name
31
- end
32
-
33
- # Build objects by <code>build</code> strategy in FactoryBot from a blueprint and declare them using RSpec's
34
- # <code>let</code>.
35
- # @param map [Hash{Symbol => Object}]
36
- # map data structure from source blueprints to instance definitions.
37
- # Each instance will be built with <code>FactoryBot::Blueprint.build(__send__(source))</code>
38
- # @example
39
- # RSpec.describe "something" do
40
- # let_blueprint(:blog_bp) do
41
- # # ... Write some DSL ...
42
- # end
43
- #
44
- # # Simplest example:
45
- # # This is equivalent to `let_blueprint_build blog_bp: { result: :blog }`
46
- # let_blueprint_build blog_bp: :blog
47
- #
48
- # # Another shorthand example:
49
- # # This is equivalent to `let_blueprint_build blog_bp: { items: %i[blog article] }`
50
- # let_blueprint_build blog_bp: %i[blog article]
51
- #
52
- # # Most flexible example:
53
- # # :result specifies the name of the result object to be declared. Defaults to nil
54
- # # :items specifies the names of the objects to be declared. Defaults to []
55
- # # :instance specifies the name of the instance object to be declared. Defaults to :"#{source}_instance"
56
- # let_blueprint_build blog_bp: { result: :blog, items: %i[article], instance: :blog_instance }
57
- #
58
- # # Above example will be expanded to:
59
- # let(:blog_instance) { ::FactoryBot::Blueprint.build(blog_bp) } # the instance object
60
- # let(:blog) { blog_instance[Factrey::Blueprint::Node::RESULT_NAME] } # the result object
61
- # let(:article) { blog_instance[:article] } # the item objects
62
- # end
63
- def let_blueprint_build(**map) = let_blueprint_instantiate(:build, **map)
64
-
65
- # Build objects by <code>create</code> strategy in FactoryBot from a blueprint and declare them using RSpec's
66
- # <code>let</code>.
67
- # See {#let_blueprint_build} for more details.
68
- # @param map [Hash{Symbol => Object}]
69
- # map data structure from source blueprints to instance definitions.
70
- # Each instance will be built with <code>FactoryBot::Blueprint.build(__send__(source))</code>
71
- def let_blueprint_create(**map) = let_blueprint_instantiate(:create, **map)
72
-
73
- # @!visibility private
74
- def let_blueprint_instantiate(strategy, **map)
75
- raise ArgumentError, "Unsupported strategy: #{strategy}" if strategy && !%i[create build].include?(strategy)
76
-
77
- map.map do |source, definition|
78
- raise TypeError, "source must be a Symbol" unless source.is_a?(Symbol)
79
-
80
- definition =
81
- case definition
82
- when Symbol
83
- { result: definition }
84
- when Array
85
- { items: definition }
86
- when Hash
87
- definition
88
- else
89
- raise TypeError, "definition must be one of Symbol, Array, Hash"
90
- end
91
-
92
- result_name = definition[:result]
93
- item_names = definition[:items] || []
94
- instance = definition[:instance] || :"#{source}_instance"
95
-
96
- raise TypeError, "result must be a Symbol" if result_name && !result_name.is_a?(Symbol)
97
- if !item_names.is_a?(Array) || !item_names.all? { _1.is_a?(Symbol) }
98
- raise TypeError, "items must be an Array of Symbols"
99
- end
100
- raise TypeError, "instance must be a Symbol" unless instance.is_a?(Symbol)
101
-
102
- if strategy # If no strategy is specified, the instance is assumed to exist
103
- let(instance) { ::FactoryBot::Blueprint.instantiate(strategy, __send__(source)) }
104
- end
105
-
106
- let(result_name) { __send__(instance)[::Factrey::Blueprint::Node::RESULT_NAME] } if result_name
107
-
108
- item_names.each { |name| let(name) { __send__(instance)[name] } }
109
-
110
- instance
111
- end
112
- end
113
-
114
- # Write the blueprint in DSL, create an instance of it, and declare each object of the instance using RSpec's
115
- # <code>let</code>.
9
+ # This method expresses that the names given as arguments will be used in the let declaration for the set of
10
+ # objects to be created from the blueprint. Subsequent method calls specify specifically how to use FactoryBot
11
+ # to create the set of objects from the blueprint.
116
12
  #
117
- # This is a shorthand for {#let_blueprint} with {#let_blueprint_build} or {#let_blueprint_create}.
118
- # @param name [Symbol]
119
- # name of the result object to be declared using RSpec's <code>let</code>.
120
- # It is also used as a name prefix of the blueprint
13
+ # @param name [Symbol] name of the result object to be declared using RSpec's <code>let</code>
121
14
  # @param items [Array<Symbol>] names of the objects to be declared using RSpec's <code>let</code>
122
- # @param inherit [Boolean] whether to extend the blueprint by <code>super()</code>
123
- # @param strategy [:create, :build]
124
- # FactoryBot strategy to use when building objects.
125
- # This option is ignored if <code>inherit: true</code>
126
- # @yield Write Blueprint DSL code here
15
+ # @return [Letbp]
127
16
  # @example
128
17
  # RSpec.describe "something" do
129
- # letbp(:blog, %i[article]) do
18
+ # letbp(:blog, %i[article]).build do
130
19
  # blog(title: "Daily log") do
131
20
  # let.article(title: "Article 1")
132
21
  # article(title: "Article 2")
@@ -134,48 +23,43 @@ module FactoryBot
134
23
  # end
135
24
  # end
136
25
  #
137
- # # Above example will be expanded to:
138
- # let_blueprint(:blog_blueprint) do
139
- # blog(title: "Daily log") do
140
- # let.article(title: "Article 1")
141
- # article(title: "Article 2")
142
- # article(title: "Article 3")
26
+ # # Above example will be expanded to ...
27
+ #
28
+ # # Create a blueprint:
29
+ # let(:_letbp_blog_blueprint) do
30
+ # FactoryBot::Blueprint.plan(ext: self) do
31
+ # blog(title: "Daily log") do
32
+ # let.article(title: "Article 1")
33
+ # article(title: "Article 2")
34
+ # article(title: "Article 3")
35
+ # end
143
36
  # end
144
37
  # end
145
- # let_blueprint_create blog_blueprint: { result: :blog, items: %i[article] }
38
+ #
39
+ # # Create a set of objects (with `build` build strategy) from it:
40
+ # let(:_letbp_blog_instance) { FactoryBot::Blueprint.build(_letbp_blog_blueprint) }
41
+ #
42
+ # # Declare the result object:
43
+ # let(:blog) { _letbp_blog_instance[Factrey::Blueprint::Node::RESULT_NAME] }
44
+ #
45
+ # # Declare the named objects:
46
+ # let(:article) { _letbp_blog_instance[:article] }
146
47
  # end
147
- def letbp(name, items = [], inherit: false, strategy: :create, &)
148
- raise TypeError, "name must be a Symbol" unless name.is_a?(Symbol)
149
-
150
- source = :"#{name}_blueprint"
151
- strategy = nil if inherit
152
-
153
- let_blueprint(source, inherit:, &)
154
- let_blueprint_instantiate strategy, source => { result: name, items: }
155
- end
156
-
157
- # <code>let!</code> version of {#let_blueprint}.
158
- def let_blueprint!(...)
159
- name = let_blueprint(...)
160
- before { __send__(name) }
161
- end
162
-
163
- # <code>let!</code> version of {#let_blueprint_build}.
164
- def let_blueprint_build!(...)
165
- names = let_blueprint_build(...)
166
- before { names.each { __send__(_1) } }
167
- end
168
-
169
- # <code>let!</code> version of {#let_blueprint_create}.
170
- def let_blueprint_create!(...)
171
- names = let_blueprint_create(...)
172
- before { names.each { __send__(_1) } }
173
- end
48
+ def letbp(name, items = []) = Letbp.new(self, :lazy, name, items)
174
49
 
175
50
  # <code>let!</code> version of {#letbp}.
176
- def letbp!(...)
177
- names = letbp(...)
178
- before { names.each { __send__(_1) } }
51
+ # @param name [Symbol]
52
+ # @param items [Array<Symbol>]
53
+ # @return [Letbp]
54
+ def letbp!(name, items = []) = Letbp.new(self, :eager, name, items)
55
+
56
+ # <code>let_it_be</code> version of {#letbp}. This requires {https://github.com/test-prof/test-prof test-prof}.
57
+ # @param name [Symbol]
58
+ # @param items [Array<Symbol>]
59
+ # @param options [Hash] options for <code>let_it_be</code>
60
+ # @return [Letbp]
61
+ def letbp_it_be(name, items = [], **options)
62
+ Letbp.new(self, :let_it_be, name, items, let_it_be_options: options)
179
63
  end
180
64
  end
181
65
  end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FactoryBot
4
+ module Blueprint
5
+ module RSpec
6
+ # An intermediate object for <code>letbp</code> syntax. See {Driver#letbp} for more details.
7
+ class Letbp
8
+ # @!visibility private
9
+ def initialize(context, kind, name, items, let_it_be_options: nil)
10
+ raise TypeError, "context must include FactoryBot::Blueprint::RSpec::Driver" unless context.is_a?(Driver)
11
+ raise ArgumentError, "unknown kind: #{kind}" unless %i[lazy eager let_it_be].include?(kind)
12
+ raise TypeError, "name must be a Symbol" unless name.is_a?(Symbol)
13
+ unless items.is_a?(Array) && items.all? { _1.is_a?(Symbol) }
14
+ raise TypeError, "items must be an Array of Symbols"
15
+ end
16
+ if let_it_be_options && kind != :let_it_be
17
+ raise ArgumentError, "`let_it_be_options` must be used with `let_it_be` kind"
18
+ end
19
+
20
+ @context = context
21
+ @kind = kind
22
+ @name = name
23
+ @items = items
24
+ @let_it_be_options = let_it_be_options
25
+ end
26
+
27
+ # Create a new blueprint, and create a set of objects (with <code>build</code> build strategy) from it.
28
+ # @yield Write Blueprint DSL code here
29
+ # @example
30
+ # letbp(:blog, %i[article]).build do
31
+ # blog(title: "Daily log") do
32
+ # let.article(title: "Article 1")
33
+ # article(title: "Article 2")
34
+ # article(title: "Article 3")
35
+ # end
36
+ # end
37
+ def build(&)
38
+ let_blueprint(:new, &)
39
+ let_instance(:build)
40
+ let_objects
41
+ end
42
+
43
+ # Create a new blueprint, and create a set of objects (with <code>build_stubbed</code> build strategy) from it.
44
+ # @yield Write Blueprint DSL code here
45
+ def build_stubbed(&)
46
+ let_blueprint(:new, &)
47
+ let_instance(:build_stubbed)
48
+ let_objects
49
+ end
50
+
51
+ # Create a new blueprint, and create a set of objects (with <code>create</code> build strategy) from it.
52
+ # @yield Write Blueprint DSL code here
53
+ def create(&)
54
+ let_blueprint(:new, &)
55
+ let_instance(:create)
56
+ let_objects
57
+ end
58
+
59
+ # Create a set of objects (with <code>build</code> build strategy) from an existing blueprint.
60
+ # @yield Usual let context for retrieving an existing blueprint
61
+ # @example
62
+ # let(:blog_blueprint) do
63
+ # bp.plan do
64
+ # blog(title: "Daily log") do
65
+ # let.article(title: "Article 1")
66
+ # article(title: "Article 2")
67
+ # article(title: "Article 3")
68
+ # end
69
+ # end
70
+ # end
71
+ # letbp(:blog, %i[article]).build_from { blog_blueprint }
72
+ def build_from(&)
73
+ let_blueprint(:from, &)
74
+ let_instance(:build)
75
+ let_objects
76
+ end
77
+
78
+ # Create a set of objects (with <code>build_stubbed</code> build strategy) from an existing blueprint.
79
+ # @yield Usual let context for retrieving an existing blueprint
80
+ def build_stubbed_from(&)
81
+ let_blueprint(:from, &)
82
+ let_instance(:build_stubbed)
83
+ let_objects
84
+ end
85
+
86
+ # Create a set of objects (with <code>create</code> build strategy) from an existing blueprint.
87
+ # @yield Usual let context for retrieving an existing blueprint
88
+ def create_from(&)
89
+ let_blueprint(:from, &)
90
+ let_instance(:create)
91
+ let_objects
92
+ end
93
+
94
+ # Extend <code>super()</code> blueprint.
95
+ # @yield Write Blueprint DSL code here
96
+ # @example
97
+ # RSpec.describe "something" do
98
+ # letbp(:blog, %i[article]).build do
99
+ # blog(title: "Daily log") do
100
+ # let.article(title: "Article")
101
+ # end
102
+ # end
103
+ #
104
+ # context "with a comment on the article" do
105
+ # letbp(:blog, %i[comment]).inherit do
106
+ # on.article do
107
+ # let.comment(text: "Comment")
108
+ # end
109
+ # end
110
+ # end
111
+ # end
112
+ def inherit(&)
113
+ let_blueprint(:inherit, &)
114
+ # We don't need `let_instance` here because it's already defined in the parent context
115
+ let_objects
116
+ end
117
+
118
+ private
119
+
120
+ def blueprint_name = :"_letbp_#{@name}_blueprint"
121
+ def instance_name = :"_letbp_#{@name}_instance"
122
+
123
+ def let(var, &)
124
+ case @kind
125
+ when :lazy
126
+ @context.let(var, &)
127
+ when :eager
128
+ @context.let!(var, &)
129
+ when :let_it_be
130
+ @context.let_it_be(var, **@let_it_be_options, &)
131
+ end
132
+ end
133
+
134
+ # @param source [:new, :from, :inherit]
135
+ def let_blueprint(source, &)
136
+ case source
137
+ when :new
138
+ let(blueprint_name) { ::FactoryBot::Blueprint.plan(ext: self, &) }
139
+ when :from
140
+ let(blueprint_name, &)
141
+ when :inherit
142
+ raise ArgumentError, "#inherit does not work with `letbp_it_be`" if @kind == :let_it_be
143
+
144
+ let(blueprint_name) { ::FactoryBot::Blueprint.plan(super(), ext: self, &) }
145
+ end
146
+ end
147
+
148
+ # @param build_strategy [:build, :build_stubbed, :create]
149
+ def let_instance(build_strategy)
150
+ blueprint_name = self.blueprint_name
151
+ let(instance_name) { ::FactoryBot::Blueprint.instantiate(build_strategy, __send__(blueprint_name)) }
152
+ end
153
+
154
+ def let_objects
155
+ instance_name = self.instance_name
156
+
157
+ let(@name) { __send__(instance_name)[::Factrey::Blueprint::Node::RESULT_NAME] }
158
+ @items.each { |item| let(item) { __send__(instance_name)[item] } }
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -3,7 +3,7 @@
3
3
  module FactoryBot
4
4
  module Blueprint
5
5
  module RSpec
6
- VERSION = "0.4.0"
6
+ VERSION = "0.5.0"
7
7
  end
8
8
  end
9
9
  end
@@ -2,4 +2,5 @@
2
2
 
3
3
  require "factory_bot/blueprint"
4
4
  require_relative "rspec/version"
5
+ require_relative "rspec/letbp"
5
6
  require_relative "rspec/driver"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factory_bot-blueprint-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yubrot
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-13 00:00:00.000000000 Z
11
+ date: 2024-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: factory_bot-blueprint
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.4.0
19
+ version: 0.5.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.4.0
26
+ version: 0.5.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec-core
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -49,6 +49,7 @@ files:
49
49
  - README.md
50
50
  - lib/factory_bot/blueprint/rspec.rb
51
51
  - lib/factory_bot/blueprint/rspec/driver.rb
52
+ - lib/factory_bot/blueprint/rspec/letbp.rb
52
53
  - lib/factory_bot/blueprint/rspec/version.rb
53
54
  homepage: https://github.com/yubrot/factory_bot-blueprint
54
55
  licenses:
@@ -66,14 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
67
  requirements:
67
68
  - - ">="
68
69
  - !ruby/object:Gem::Version
69
- version: 3.1.0
70
+ version: 3.2.0
70
71
  required_rubygems_version: !ruby/object:Gem::Requirement
71
72
  requirements:
72
73
  - - ">="
73
74
  - !ruby/object:Gem::Version
74
75
  version: '0'
75
76
  requirements: []
76
- rubygems_version: 3.5.11
77
+ rubygems_version: 3.5.22
77
78
  signing_key:
78
79
  specification_version: 4
79
80
  summary: FactoryBot::Blueprint integration for RSpec