lib/convenient_service/rspec/matchers/custom/results/base.rb in convenient_service-0.12.0 vs lib/convenient_service/rspec/matchers/custom/results/base.rb in convenient_service-0.13.0

- old
+ new

@@ -1,314 +1,277 @@ # frozen_string_literal: true -require_relative "base/commands" require_relative "base/constants" -require_relative "base/errors" +require_relative "base/entities" +require_relative "base/exceptions" module ConvenientService module RSpec module Matchers module Custom module Results class Base include Support::AbstractMethod ## + # @api private + # + # @!attribute [r] result + # @return [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result] + # + attr_reader :result + + ## + # @api private + # # @return [String, Symbol] # abstract_method :statuses ## - # @param result [ConvenientService::Service::Plugins::HasResult::Entities::Result] + # @api private + # + # @return [void] + # + def initialize(statuses: self.statuses, result: nil) + chain.statuses = statuses + + @result = result + end + + ## + # @api public + # + # @param result [ConvenientService::Service::Plugins::HasJSendResult::Entities::Result] # @return [Boolean] # def matches?(result) @result = result - rules = [] - ## - # IMPORTANT: Makes `result.class.include?` from the following line idempotent. + # IMPORTANT: Makes `result.class.include?` idempotent. # - result.commit_config!(trigger: Constants::Triggers::BE_RESULT) if result.respond_to?(:commit_config!) - - rules << ->(result) { result.class.include?(Service::Plugins::HasResult::Entities::Result::Concern) } - - ## - # IMPORTANT: Result status is NOT marked as checked intentionally, since it is a mutable operation. + # TODO: Explainer when `result.class.commit_config!` is required. It was introduced in panic when the first thread-safety issues occurred. Looks like it is an outdated operation now. It is probably useful only when a config has almost zero plugins. # - rules << ->(result) { result.status.in?(statuses) } + # TODO: Resolve a bug in `delegate_to`. It makes `respond_to?` always return `true`, even for classes that do NOT have the `commit_config!` method. See specs for details. + # + # let(:result) { "foo" } + # + # specify do + # expect { matcher.matches?(result) } + # .not_to delegate_to(result.class, :commit_config!) + # .with_any_arguments + # .without_calling_original + # end + # + # `result.class.method(:commit_config!)` + # # => <Method: String.commit_config!(*args, &block) /usr/local/bundle/gems/rspec-mocks-3.11.2/lib/rspec/mocks/method_double.rb:63> + # + result.class.commit_config!(trigger: Constants::Triggers::BE_RESULT) if result.class.respond_to?(:commit_config!) - rules << ->(result) { result.service.instance_of?(service_class) } if used_of_service? - rules << ->(result) { Commands::MatchResultStep.call(result: result, step: step) } if used_of_step? - rules << ->(result) { result.unsafe_data == data } if used_data? - rules << ->(result) { result.unsafe_message == message } if used_message? - rules << ->(result) { result.unsafe_code == code } if used_code? - - condition = Utils::Proc.conjunct(rules) - - condition.call(result) + validator.valid_result? end ## + # @api public + # # @return [String] # def description - expected_parts + printer.description end ## + # @api public + # # @return [String] # def failure_message - "expected that `#{result.service.class}` result would #{default_text}" + printer.failure_message end ## + # @api public + # # @return [String] # # @internal - # https://relishapp.com/rspec/rspec-expectations/v/3-11/docs/custom-matchers/define-a-custom-matcher#overriding-the-failure-message-when-negated + # - https://relishapp.com/rspec/rspec-expectations/v/3-11/docs/custom-matchers/define-a-custom-matcher#overriding-the-failure-message-when-negated # def failure_message_when_negated - "expected that `#{result.service.class}` result would NOT #{default_text}" + printer.failure_message_when_negated end ## + # @api public + # # @param data [Hash] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def with_data(data) - chain[:data] = data + chain.data = data self end ## + # @api public + # # @param data [Hash] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def and_data(data) - chain[:data] = data + chain.data = data self end ## + # @api public + # # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def without_data - chain[:data] = {} + chain.data = {} self end ## + # @api public + # # @param message [String] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def with_message(message) - chain[:message] = message + chain.message = message self end ## + # @api public + # # @param message [String] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def and_message(message) - chain[:message] = message + chain.message = message self end ## + # @api public + # # @return [String, Symbol] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def with_code(code) - chain[:code] = code + chain.code = code self end ## + # @api public + # # @return [String, Symbol] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def and_code(code) - chain[:code] = code + chain.code = code self end ## - # @param service_class [Class] + # @api public + # + # @param service [ConvenientService::Service] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # - def of_service(service_class) - chain[:service_class] = service_class + def of_service(service) + chain.service = service self end ## + # @api public + # # @param step [Class, Symbol] # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def of_step(step) - chain[:step] = step + chain.step = step self end ## + # @api public + # # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # def without_step - chain[:step] = nil + chain.step = nil self end - private - ## - # @!attribute [r] result - # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result] + # @api public # - attr_reader :result - - ## - # @return [String] + # @param comparison_method [Symbo, String] + # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base] # - def default_text - expected_parts << "\n\n" << got_parts - end + def comparing_by(comparison_method) + chain.comparison_method = comparison_method - ## - # @return [String] - # - # @internal - # TODO: Align for easier visual comparison. - # TODO: New line for each attribute. - # - def expected_parts - parts = [] - - parts << "be #{printable_statuses}" - parts << "of service `#{service_class}`" if used_of_service? - parts << Commands::GenerateExpectedStepPart.call(step: step) if used_of_step? - parts << "with data `#{data}`" if used_data? - parts << "with message `#{message}`" if used_message? - parts << "with code `#{code}`" if used_code? - - parts.join(" ") + self end ## - # @return [String] + # @api private # - # @internal - # TODO: Align for easier visual comparison. - # TODO: New line for each attribute. + # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base::Entities::Chain] # - def got_parts - parts = [] - - parts << "got `#{result.status}`" - parts << "of service `#{result.service.class}`" if used_of_service? - parts << Commands::GenerateGotStepPart.call(result: result) if used_of_step? - parts << "with data `#{result.unsafe_data}`" if used_data? - parts << "with message `#{result.unsafe_message}`" if used_message? - parts << "with code `#{result.unsafe_code}`" if used_code? - - parts.join(" ") + def chain + @chain ||= Entities::Chain.new end ## - # @return [Boolean] + # @api private # - def used_data? - chain.key?(:data) - end - - ## - # @return [Boolean] + # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base::Entities::Printers::Base] # - def used_message? - chain.key?(:message) + def printer + @printer ||= Entities::Printers.create(matcher: self) end ## - # @return [Boolean] + # @api private # - def used_code? - chain.key?(:code) - end - - ## - # @return [Boolean] + # @return [ConvenientService::RSpec::Matchers::Custom::Results::Base::Entities::Validator] # - def used_of_service? - chain.key?(:service_class) + def validator + @validator ||= Entities::Validator.new(matcher: self) end ## - # @return [Boolean] + # @api private # - def used_of_step? - chain.key?(:step) - end - - ## - # @return [Hash] + # @param other [Object] Can be any type. + # @return [Boolean, nil] # - def data - @data ||= chain[:data] || {} - end + def ==(other) + return unless other.instance_of?(self.class) - ## - # @return [String] - # - def message - @message ||= chain[:message] || "" - end + return false if chain != other.chain + return false if result != other.result - ## - # @return [String, Symbol] - # - def code - @code ||= chain[:code] || "" - end - - ## - # @return [Class] - # - def service_class - Utils::Object.instance_variable_fetch(self, :@service_class) { chain[:service_class] } - end - - ## - # @return [Class] - # - def step - Utils::Object.instance_variable_fetch(self, :@step) { chain[:step] } - end - - ## - # @return [Hash] - # - def chain - @chain ||= {} - end - - ## - # @return [String] - # - def printable_statuses - statuses.map { |status| "`#{status}`" }.join(" or ") + true end end end end end