From 439cfb49ef76ef6a9f7aeb8d6d344670f7695713 Mon Sep 17 00:00:00 2001 From: Rodrigo Serradura Date: Mon, 1 Jan 2024 19:12:49 -0300 Subject: [PATCH 1/7] Isolate transitions duration test --- .github/workflows/main.yml | 6 ++++-- Rakefile | 10 ++++++++-- test/bcdd/result/transitions/duration_test.rb | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd71c23..54c6352 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,10 +21,12 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - name: Run BCDD::Result.configuration test (Minitest) - run: bundle exec rake test_configuration TEST_CONFIG_FREEZING=true - name: Run tests (Minitest) run: bundle exec rake test + - name: Run BCDD::Result.configuration test (Minitest) + run: bundle exec rake test_configuration TEST_CONFIG_FREEZING=true + - name: Run BCDD::Result.transitions test (Minitest) + run: bundle exec rake test_transitions_duration BCDD_RESULT_TEST_TRANSITIONS_DURATION=true - name: Run static code analysis (Rubocop) run: bundle exec rake rubocop - name: Run static type checking (Steep) diff --git a/Rakefile b/Rakefile index 5192bef..b37965b 100644 --- a/Rakefile +++ b/Rakefile @@ -3,16 +3,22 @@ require 'bundler/gem_tasks' require 'rake/testtask' +Rake::TestTask.new(:test) do |t| + t.libs += %w[lib test] + + t.test_files = FileList.new('test/**/*_test.rb') +end + Rake::TestTask.new(:test_configuration) do |t| t.libs += %w[lib test] t.test_files = FileList.new('test/**/configuration_test.rb') end -Rake::TestTask.new(:test) do |t| +Rake::TestTask.new(:test_transitions_duration) do |t| t.libs += %w[lib test] - t.test_files = FileList.new('test/**/*_test.rb') + t.test_files = FileList.new('test/**/duration_test.rb') end require 'rubocop/rake_task' diff --git a/test/bcdd/result/transitions/duration_test.rb b/test/bcdd/result/transitions/duration_test.rb index cbfd653..3cbe738 100644 --- a/test/bcdd/result/transitions/duration_test.rb +++ b/test/bcdd/result/transitions/duration_test.rb @@ -6,6 +6,8 @@ class BCDD::Result::TransitionsDurationTest < Minitest::Test Sleep = -> { sleep(0.1).then { BCDD::Result::Success(:ok) } } test '#duration' do + return unless ENV['BCDD_RESULT_TEST_TRANSITIONS_DURATION'] + result = BCDD::Result.transitions do Sleep .call From d86b3661743b29e4dc06a74f9959f7512360d2d9 Mon Sep 17 00:00:00 2001 From: Rodrigo Serradura Date: Mon, 1 Jan 2024 16:42:29 -0300 Subject: [PATCH 2/7] Rename Continuable to Continue --- lib/bcdd/result/context/expectations/mixin.rb | 4 ++-- lib/bcdd/result/context/mixin.rb | 4 ++-- lib/bcdd/result/expectations/mixin.rb | 4 ++-- lib/bcdd/result/mixin.rb | 4 ++-- sig/bcdd/result/context.rbs | 4 ++-- sig/bcdd/result/expectations.rbs | 2 +- sig/bcdd/result/mixin.rbs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/bcdd/result/context/expectations/mixin.rb b/lib/bcdd/result/context/expectations/mixin.rb index 6a22eee..ec22f5e 100644 --- a/lib/bcdd/result/context/expectations/mixin.rb +++ b/lib/bcdd/result/context/expectations/mixin.rb @@ -7,13 +7,13 @@ module Expectations::Mixin Methods = BCDD::Result::Expectations::Mixin::Methods module Addons - module Continuable + module Continue private def Continue(**value) Success.new(type: :continued, value: value, subject: self) end end - OPTIONS = { continue: Continuable }.freeze + OPTIONS = { continue: Continue }.freeze def self.options(config_flags) ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/lib/bcdd/result/context/mixin.rb b/lib/bcdd/result/context/mixin.rb index e7c8727..36b59b2 100644 --- a/lib/bcdd/result/context/mixin.rb +++ b/lib/bcdd/result/context/mixin.rb @@ -19,7 +19,7 @@ def Failure(type, **value) end module Addons - module Continuable + module Continue def Success(type, **value) _ResultAs(Success, type, value, terminal: true) end @@ -29,7 +29,7 @@ def Success(type, **value) end end - OPTIONS = { continue: Continuable }.freeze + OPTIONS = { continue: Continue }.freeze def self.options(config_flags) ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/lib/bcdd/result/expectations/mixin.rb b/lib/bcdd/result/expectations/mixin.rb index de9dc3a..fbe6d7b 100644 --- a/lib/bcdd/result/expectations/mixin.rb +++ b/lib/bcdd/result/expectations/mixin.rb @@ -36,13 +36,13 @@ def self.to_eval(addons) end module Addons - module Continuable + module Continue private def Continue(value) Success.new(type: :continued, value: value, subject: self) end end - OPTIONS = { continue: Continuable }.freeze + OPTIONS = { continue: Continue }.freeze def self.options(config_flags) Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/lib/bcdd/result/mixin.rb b/lib/bcdd/result/mixin.rb index 908d361..257b44b 100644 --- a/lib/bcdd/result/mixin.rb +++ b/lib/bcdd/result/mixin.rb @@ -26,7 +26,7 @@ def Failure(type, value = nil) end module Addons - module Continuable + module Continue def Success(type, value = nil) _ResultAs(Success, type, value, terminal: true) end @@ -36,7 +36,7 @@ def Success(type, value = nil) end end - OPTIONS = { continue: Continuable }.freeze + OPTIONS = { continue: Continue }.freeze def self.options(config_flags) Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/sig/bcdd/result/context.rbs b/sig/bcdd/result/context.rbs index 56f30ca..3b5b58d 100644 --- a/sig/bcdd/result/context.rbs +++ b/sig/bcdd/result/context.rbs @@ -58,7 +58,7 @@ class BCDD::Result::Context end module Addons - module Continuable + module Continue include BCDD::Result::Context::Mixin::Methods private @@ -91,7 +91,7 @@ module BCDD::Result::Context::Expectations::Mixin Factory: singleton(BCDD::Result::Expectations::Mixin::Factory) module Addons - module Continuable + module Continue private def Continue: (**untyped) -> BCDD::Result::Context::Success end diff --git a/sig/bcdd/result/expectations.rbs b/sig/bcdd/result/expectations.rbs index c14cacc..b4e947f 100644 --- a/sig/bcdd/result/expectations.rbs +++ b/sig/bcdd/result/expectations.rbs @@ -56,7 +56,7 @@ module BCDD::Result::Expectations::Mixin end module Addons - module Continuable + module Continue private def Continue: (untyped) -> BCDD::Result::Success end diff --git a/sig/bcdd/result/mixin.rbs b/sig/bcdd/result/mixin.rbs index acdf990..ad6fa52 100644 --- a/sig/bcdd/result/mixin.rbs +++ b/sig/bcdd/result/mixin.rbs @@ -15,7 +15,7 @@ class BCDD::Result end module Addons - module Continuable + module Continue include BCDD::Result::Mixin::Methods private From 76ba963958916efe6e2a8a81333e1c43842dcd12 Mon Sep 17 00:00:00 2001 From: Rodrigo Serradura Date: Mon, 1 Jan 2024 16:50:20 -0300 Subject: [PATCH 3/7] Change the continue addon config data --- lib/bcdd/result/config/switchers/addons.rb | 12 ++++++++---- test/bcdd/result/config/addon_test.rb | 17 ++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/bcdd/result/config/switchers/addons.rb b/lib/bcdd/result/config/switchers/addons.rb index 45bf356..ade6e98 100644 --- a/lib/bcdd/result/config/switchers/addons.rb +++ b/lib/bcdd/result/config/switchers/addons.rb @@ -3,11 +3,15 @@ class BCDD::Result class Config module Addons + AFFECTS = %w[ + BCDD::Result.mixin + BCDD::Result::Context.mixin + BCDD::Result::Expectations.mixin + BCDD::Result::Context::Expectations.mixin + ].freeze + OPTIONS = { - continue: { - default: false, - affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations] - } + continue: { default: false, affects: AFFECTS } }.transform_values!(&:freeze).freeze def self.switcher diff --git a/test/bcdd/result/config/addon_test.rb b/test/bcdd/result/config/addon_test.rb index 680c653..4b28aed 100644 --- a/test/bcdd/result/config/addon_test.rb +++ b/test/bcdd/result/config/addon_test.rb @@ -4,6 +4,13 @@ class BCDD::Result::Config class AddonTest < Minitest::Test + AFFECTS = [ + 'BCDD::Result.mixin', + 'BCDD::Result::Context.mixin', + 'BCDD::Result::Expectations.mixin', + 'BCDD::Result::Context::Expectations.mixin' + ].freeze + test 'the switcher' do config = BCDD::Result.config.addon @@ -11,15 +18,7 @@ class AddonTest < Minitest::Test assert_equal( { - continue: { - enabled: false, - affects: [ - 'BCDD::Result', - 'BCDD::Result::Context', - 'BCDD::Result::Expectations', - 'BCDD::Result::Context::Expectations' - ] - } + continue: { enabled: false, affects: AFFECTS } }, config.options ) From f9ef26b0d10ffa22d6122aaec96057821b935751 Mon Sep 17 00:00:00 2001 From: Rodrigo Serradura Date: Mon, 1 Jan 2024 17:15:00 -0300 Subject: [PATCH 4/7] Improve tests of config addon --- .../continue/expectations_test.rb} | 2 +- .../continue/instance_test.rb} | 2 +- .../continue/singleton_test.rb} | 2 +- .../bcdd/result/config/addon/continue_test.rb | 118 +++++++++++++++--- .../continue/expectations_test.rb} | 2 +- .../continue/instance_test.rb} | 2 +- .../continue/singleton_test.rb} | 2 +- 7 files changed, 108 insertions(+), 22 deletions(-) rename test/bcdd/result/{expectations/with_subject/continue_test.rb => addons/continue/expectations_test.rb} (99%) rename test/bcdd/result/{and_then/with_subject/continue_instance_test.rb => addons/continue/instance_test.rb} (98%) rename test/bcdd/result/{and_then/with_subject/continue_singleton_test.rb => addons/continue/singleton_test.rb} (98%) rename test/bcdd/result/context/{expectations/with_subject/continue_test.rb => addons/continue/expectations_test.rb} (99%) rename test/bcdd/result/context/{and_then/with_subject/continue_instance_test.rb => addons/continue/instance_test.rb} (98%) rename test/bcdd/result/context/{and_then/with_subject/continue_singleton_test.rb => addons/continue/singleton_test.rb} (98%) diff --git a/test/bcdd/result/expectations/with_subject/continue_test.rb b/test/bcdd/result/addons/continue/expectations_test.rb similarity index 99% rename from test/bcdd/result/expectations/with_subject/continue_test.rb rename to test/bcdd/result/addons/continue/expectations_test.rb index 086c5f0..e85d667 100644 --- a/test/bcdd/result/expectations/with_subject/continue_test.rb +++ b/test/bcdd/result/addons/continue/expectations_test.rb @@ -2,7 +2,7 @@ require 'test_helper' -class BCDD::Result::ExpectationsWithSubjectSuccessTypeTest < Minitest::Test +class BCDD::Result::AddonsContinueExpectationsTest < Minitest::Test class DivideType include BCDD::Result::Expectations.mixin( config: { addon: { continue: true } }, diff --git a/test/bcdd/result/and_then/with_subject/continue_instance_test.rb b/test/bcdd/result/addons/continue/instance_test.rb similarity index 98% rename from test/bcdd/result/and_then/with_subject/continue_instance_test.rb rename to test/bcdd/result/addons/continue/instance_test.rb index 351920c..cedcedc 100644 --- a/test/bcdd/result/and_then/with_subject/continue_instance_test.rb +++ b/test/bcdd/result/addons/continue/instance_test.rb @@ -2,7 +2,7 @@ require 'test_helper' -class BCDD::Result::AndThenWithSubjectContinueInstanceTest < Minitest::Test +class BCDD::Result::AddonsContinueInstanceTest < Minitest::Test class Divide include BCDD::Result.mixin(config: { addon: { continue: true } }) diff --git a/test/bcdd/result/and_then/with_subject/continue_singleton_test.rb b/test/bcdd/result/addons/continue/singleton_test.rb similarity index 98% rename from test/bcdd/result/and_then/with_subject/continue_singleton_test.rb rename to test/bcdd/result/addons/continue/singleton_test.rb index 9286c6d..950a485 100644 --- a/test/bcdd/result/and_then/with_subject/continue_singleton_test.rb +++ b/test/bcdd/result/addons/continue/singleton_test.rb @@ -2,7 +2,7 @@ require 'test_helper' -class BCDD::Result::AndThenWithSubjectContinueSingletonTest < Minitest::Test +class BCDD::Result::AddonsContinueSingletonTest < Minitest::Test module Divide extend self, BCDD::Result.mixin(config: { addon: { continue: true } }) diff --git a/test/bcdd/result/config/addon/continue_test.rb b/test/bcdd/result/config/addon/continue_test.rb index 283e500..affb7cd 100644 --- a/test/bcdd/result/config/addon/continue_test.rb +++ b/test/bcdd/result/config/addon/continue_test.rb @@ -5,46 +5,132 @@ class BCDD::Result::Config class AddonContinueTest < Minitest::Test test 'the side effects' do - class1 = Class.new do + BCDD::Result.config.addon.disable!(:continue) + + class1a = Class.new do include BCDD::Result.mixin def call - Continue('the method Continue does not exist as the config is disabled') + Continue("this method won't exist as the default config is disabled") end end - error1 = assert_raises(NoMethodError) { class1.new.call } + class2a = Class.new do + include BCDD::Result::Expectations.mixin - assert_match(/undefined method.+Continue/, error1.message) + def call + Continue("this method won't exist as the default config is disabled") + end + end + + class3a = Class.new do + include BCDD::Result::Context.mixin + + def call + Continue(msg: "this method won't exist as the default config is disabled") + end + end + + class4a = Class.new do + include BCDD::Result::Context::Expectations.mixin + + def call + Continue(msg: "this method won't exist as the default config is disabled") + end + end + + error1a = assert_raises(NoMethodError) { class1a.new.call } + error2a = assert_raises(NoMethodError) { class2a.new.call } + error3a = assert_raises(NoMethodError) { class3a.new.call } + error4a = assert_raises(NoMethodError) { class4a.new.call } + + assert_match(/undefined method.+Continue/, error1a.message) + assert_match(/undefined method.+Continue/, error2a.message) + assert_match(/undefined method.+Continue/, error3a.message) + assert_match(/undefined method.+Continue/, error4a.message) BCDD::Result.config.addon.enable!(:continue) - class2 = Class.new do + class1b = Class.new do include BCDD::Result.mixin def call - Continue('the method Continue now exists as the config is enabled by default') + Continue('this method will exist as the config is enabled by default') end end - assert_equal( - 'the method Continue now exists as the config is enabled by default', - class2.new.call.value - ) + class2b = Class.new do + include BCDD::Result::Expectations.mixin + + def call + Continue('this method will exist as the config is enabled by default') + end + end + + class3b = Class.new do + include BCDD::Result::Context.mixin + + def call + Continue(msg: 'this method will exist as the config is enabled by default') + end + end + + class4b = Class.new do + include BCDD::Result::Context::Expectations.mixin + + def call + Continue(msg: 'this method will exist as the config is enabled by default') + end + end + + assert_equal('this method will exist as the config is enabled by default', class1b.new.call.value) + assert_equal('this method will exist as the config is enabled by default', class2b.new.call.value) + assert_equal('this method will exist as the config is enabled by default', class3b.new.call.value[:msg]) + assert_equal('this method will exist as the config is enabled by default', class4b.new.call.value[:msg]) ensure BCDD::Result.config.addon.disable!(:continue) - class3 = Class.new do + class1c = Class.new do include BCDD::Result.mixin def call - Continue('the error is raised again as the config is disabled by default') + Continue("this method won't exist as the default config is disabled") end end - error2 = assert_raises(NoMethodError) { class3.new.call } + class2c = Class.new do + include BCDD::Result::Expectations.mixin - assert_match(/undefined method.+Continue/, error2.message) + def call + Continue("this method won't exist as the default config is disabled") + end + end + + class3c = Class.new do + include BCDD::Result::Context.mixin + + def call + Continue(msg: "this method won't exist as the default config is disabled") + end + end + + class4c = Class.new do + include BCDD::Result::Context::Expectations.mixin + + def call + Continue(msg: "this method won't exist as the default config is disabled") + end + end + + error1c = assert_raises(NoMethodError) { class1c.new.call } + error2c = assert_raises(NoMethodError) { class2c.new.call } + error3c = assert_raises(NoMethodError) { class3c.new.call } + error4c = assert_raises(NoMethodError) { class4c.new.call } + + assert_match(/undefined method.+Continue/, error1c.message) + assert_match(/undefined method.+Continue/, error2c.message) + assert_match(/undefined method.+Continue/, error3c.message) + assert_match(/undefined method.+Continue/, error4c.message) end test 'the overwriting of the default config' do @@ -70,7 +156,7 @@ def call include BCDD::Result::Context.mixin(config: { addon: { continue: false } }) def call - Continue("this method won't exist as the default config was overwritten") + Continue(msg: "this method won't exist as the default config was overwritten") end end @@ -78,7 +164,7 @@ def call include BCDD::Result::Context::Expectations.mixin(config: { addon: { continue: false } }) def call - Continue("this method won't exist as the default config was overwritten") + Continue(msg: "this method won't exist as the default config was overwritten") end end diff --git a/test/bcdd/result/context/expectations/with_subject/continue_test.rb b/test/bcdd/result/context/addons/continue/expectations_test.rb similarity index 99% rename from test/bcdd/result/context/expectations/with_subject/continue_test.rb rename to test/bcdd/result/context/addons/continue/expectations_test.rb index 272f720..53845f6 100644 --- a/test/bcdd/result/context/expectations/with_subject/continue_test.rb +++ b/test/bcdd/result/context/addons/continue/expectations_test.rb @@ -2,7 +2,7 @@ require 'test_helper' -class BCDD::Result::Context::ExpectationsWithSubjectSuccessTypeTest < Minitest::Test +class BCDD::Result::Context::AddonsContinueExpectationsTest < Minitest::Test class DivideType include BCDD::Result::Context::Expectations.mixin( config: { addon: { continue: true } }, diff --git a/test/bcdd/result/context/and_then/with_subject/continue_instance_test.rb b/test/bcdd/result/context/addons/continue/instance_test.rb similarity index 98% rename from test/bcdd/result/context/and_then/with_subject/continue_instance_test.rb rename to test/bcdd/result/context/addons/continue/instance_test.rb index ac0b6fa..4ab905f 100644 --- a/test/bcdd/result/context/and_then/with_subject/continue_instance_test.rb +++ b/test/bcdd/result/context/addons/continue/instance_test.rb @@ -2,7 +2,7 @@ require 'test_helper' -class BCDD::Result::Context::AndThenWithSubjectContinueInstanceTest < Minitest::Test +class BCDD::Result::Context::AddonsContinueInstanceTest < Minitest::Test class Divide include BCDD::Result::Context.mixin(config: { addon: { continue: true } }) diff --git a/test/bcdd/result/context/and_then/with_subject/continue_singleton_test.rb b/test/bcdd/result/context/addons/continue/singleton_test.rb similarity index 98% rename from test/bcdd/result/context/and_then/with_subject/continue_singleton_test.rb rename to test/bcdd/result/context/addons/continue/singleton_test.rb index 155228f..ac17bb1 100644 --- a/test/bcdd/result/context/and_then/with_subject/continue_singleton_test.rb +++ b/test/bcdd/result/context/addons/continue/singleton_test.rb @@ -2,7 +2,7 @@ require 'test_helper' -class BCDD::Result::Context::AndThenWithSubjectContinueSingletonTest < Minitest::Test +class BCDD::Result::Context::AddonsContinueSingletonTest < Minitest::Test module Divide extend self, BCDD::Result::Context.mixin(config: { addon: { continue: true } }) From 57c688838ac4d25a8937bd6cc7d9f29effaa7a70 Mon Sep 17 00:00:00 2001 From: Rodrigo Serradura Date: Mon, 1 Jan 2024 16:59:03 -0300 Subject: [PATCH 5/7] Add the Given() addon --- lib/bcdd/result/config/switchers/addons.rb | 3 +- lib/bcdd/result/context/expectations/mixin.rb | 10 +- lib/bcdd/result/context/mixin.rb | 10 +- lib/bcdd/result/expectations/mixin.rb | 8 +- lib/bcdd/result/mixin.rb | 8 +- sig/bcdd/result/config.rbs | 1 + sig/bcdd/result/context.rbs | 12 ++ sig/bcdd/result/expectations.rbs | 4 + sig/bcdd/result/mixin.rbs | 8 + .../result/addons/given/expectations_test.rb | 119 +++++++++++++ .../bcdd/result/addons/given/instance_test.rb | 59 +++++++ .../result/addons/given/singleton_test.rb | 59 +++++++ test/bcdd/result/config/addon/given_test.rb | 156 ++++++++++++++++++ test/bcdd/result/config/addon_test.rb | 3 +- test/bcdd/result/config_test.rb | 2 +- .../context/addons/given/expectations_test.rb | 130 +++++++++++++++ .../context/addons/given/instance_test.rb | 59 +++++++ .../context/addons/given/singleton_test.rb | 59 +++++++ .../with_subject/singleton/nested_test.rb | 40 ++++- 19 files changed, 734 insertions(+), 16 deletions(-) create mode 100644 test/bcdd/result/addons/given/expectations_test.rb create mode 100644 test/bcdd/result/addons/given/instance_test.rb create mode 100644 test/bcdd/result/addons/given/singleton_test.rb create mode 100644 test/bcdd/result/config/addon/given_test.rb create mode 100644 test/bcdd/result/context/addons/given/expectations_test.rb create mode 100644 test/bcdd/result/context/addons/given/instance_test.rb create mode 100644 test/bcdd/result/context/addons/given/singleton_test.rb diff --git a/lib/bcdd/result/config/switchers/addons.rb b/lib/bcdd/result/config/switchers/addons.rb index ade6e98..9541628 100644 --- a/lib/bcdd/result/config/switchers/addons.rb +++ b/lib/bcdd/result/config/switchers/addons.rb @@ -11,7 +11,8 @@ module Addons ].freeze OPTIONS = { - continue: { default: false, affects: AFFECTS } + continue: { default: false, affects: AFFECTS }, + given: { default: true, affects: AFFECTS } }.transform_values!(&:freeze).freeze def self.switcher diff --git a/lib/bcdd/result/context/expectations/mixin.rb b/lib/bcdd/result/context/expectations/mixin.rb index ec22f5e..1545f43 100644 --- a/lib/bcdd/result/context/expectations/mixin.rb +++ b/lib/bcdd/result/context/expectations/mixin.rb @@ -13,7 +13,15 @@ module Continue end end - OPTIONS = { continue: Continue }.freeze + module Given + private def Given(*values) + value = values.map(&:to_h).reduce({}) { |acc, val| acc.merge(val) } + + Success.new(type: :given, value: value, subject: self) + end + end + + OPTIONS = { continue: Continue, given: Given }.freeze def self.options(config_flags) ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/lib/bcdd/result/context/mixin.rb b/lib/bcdd/result/context/mixin.rb index 36b59b2..2a5337f 100644 --- a/lib/bcdd/result/context/mixin.rb +++ b/lib/bcdd/result/context/mixin.rb @@ -29,7 +29,15 @@ def Success(type, **value) end end - OPTIONS = { continue: Continue }.freeze + module Given + private def Given(*values) + value = values.map(&:to_h).reduce({}) { |acc, val| acc.merge(val) } + + _ResultAs(Success, :given, value) + end + end + + OPTIONS = { continue: Continue, given: Given }.freeze def self.options(config_flags) ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/lib/bcdd/result/expectations/mixin.rb b/lib/bcdd/result/expectations/mixin.rb index fbe6d7b..c469079 100644 --- a/lib/bcdd/result/expectations/mixin.rb +++ b/lib/bcdd/result/expectations/mixin.rb @@ -42,7 +42,13 @@ module Continue end end - OPTIONS = { continue: Continue }.freeze + module Given + private def Given(value) + Success.new(type: :given, value: value, subject: self) + end + end + + OPTIONS = { continue: Continue, given: Given }.freeze def self.options(config_flags) Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/lib/bcdd/result/mixin.rb b/lib/bcdd/result/mixin.rb index 257b44b..7a60650 100644 --- a/lib/bcdd/result/mixin.rb +++ b/lib/bcdd/result/mixin.rb @@ -36,7 +36,13 @@ def Success(type, value = nil) end end - OPTIONS = { continue: Continue }.freeze + module Given + private def Given(value) + _ResultAs(Success, :given, value) + end + end + + OPTIONS = { continue: Continue, given: Given }.freeze def self.options(config_flags) Config::Options.addon(map: config_flags, from: OPTIONS) diff --git a/sig/bcdd/result/config.rbs b/sig/bcdd/result/config.rbs index 8a2dca5..4a4ea3c 100644 --- a/sig/bcdd/result/config.rbs +++ b/sig/bcdd/result/config.rbs @@ -73,6 +73,7 @@ class BCDD::Result::Config::Switcher end module BCDD::Result::Config::Addons + AFFECTS: Array[String] OPTIONS: Hash[String, Hash[Symbol, untyped]] def self.switcher: -> BCDD::Result::Config::Switcher diff --git a/sig/bcdd/result/context.rbs b/sig/bcdd/result/context.rbs index 3b5b58d..0b360e9 100644 --- a/sig/bcdd/result/context.rbs +++ b/sig/bcdd/result/context.rbs @@ -66,6 +66,14 @@ class BCDD::Result::Context def Continue: (**untyped) -> BCDD::Result::Context::Success end + module Given + include BCDD::Result::Context::Mixin::Methods + + private + + def Given: (*untyped) -> BCDD::Result::Context::Success + end + OPTIONS: Hash[Symbol, Module] def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module] @@ -95,6 +103,10 @@ module BCDD::Result::Context::Expectations::Mixin private def Continue: (**untyped) -> BCDD::Result::Context::Success end + module Given + private def Given: (*untyped) -> BCDD::Result::Context::Success + end + OPTIONS: Hash[Symbol, Module] def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module] diff --git a/sig/bcdd/result/expectations.rbs b/sig/bcdd/result/expectations.rbs index b4e947f..cfee370 100644 --- a/sig/bcdd/result/expectations.rbs +++ b/sig/bcdd/result/expectations.rbs @@ -60,6 +60,10 @@ module BCDD::Result::Expectations::Mixin private def Continue: (untyped) -> BCDD::Result::Success end + module Given + private def Given: (untyped) -> BCDD::Result::Success + end + OPTIONS: Hash[Symbol, Module] def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module] diff --git a/sig/bcdd/result/mixin.rbs b/sig/bcdd/result/mixin.rbs index ad6fa52..118d474 100644 --- a/sig/bcdd/result/mixin.rbs +++ b/sig/bcdd/result/mixin.rbs @@ -23,6 +23,14 @@ class BCDD::Result def Continue: (untyped) -> BCDD::Result::Success end + module Given + include BCDD::Result::Mixin::Methods + + private + + def Given: (untyped) -> BCDD::Result::Success + end + OPTIONS: Hash[Symbol, Module] def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module] diff --git a/test/bcdd/result/addons/given/expectations_test.rb b/test/bcdd/result/addons/given/expectations_test.rb new file mode 100644 index 0000000..de4452c --- /dev/null +++ b/test/bcdd/result/addons/given/expectations_test.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result::AddonsGivenExpectationsTest < Minitest::Test + class DivideType + include BCDD::Result::Expectations.mixin( + config: { addon: { continue: true } }, + success: :ok, + failure: :err + ) + + def call(arg1, arg2) + Given([arg1, arg2]) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers(numbers) + number1, number2 = numbers + + number1.is_a?(::Numeric) or return Failure(:err, 'arg1 must be numeric') + number2.is_a?(::Numeric) or return Failure(:err, 'arg2 must be numeric') + + Continue(numbers) + end + + def validate_non_zero(numbers) + return Continue(numbers) unless numbers.last.zero? + + Failure(:err, 'arg2 must not be zero') + end + + def divide((number1, number2)) + Success(:ok, number1 / number2) + end + end + + class DivideTypes + include BCDD::Result::Expectations.mixin( + config: { addon: { continue: true } }, + success: :division_completed, + failure: %i[invalid_arg division_by_zero] + ) + + def call(arg1, arg2) + Given([arg1, arg2]) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers((arg1, arg2)) + arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric') + arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric') + + Continue([arg1, arg2]) + end + + def validate_non_zero(numbers) + return Continue(numbers) unless numbers.last.zero? + + Failure(:division_by_zero, 'arg2 must not be zero') + end + + def divide((number1, number2)) + Success(:division_completed, number1 / number2) + end + end + + module DivideTypeAndValue + extend self, BCDD::Result::Expectations.mixin( + config: { addon: { continue: true } }, + success: { division_completed: Numeric }, + failure: { invalid_arg: String, division_by_zero: String } + ) + + def call(arg1, arg2) + Given([arg1, arg2]) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers((arg1, arg2)) + arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric') + arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric') + + Continue([arg1, arg2]) + end + + def validate_non_zero(numbers) + return Continue(numbers) unless numbers.last.zero? + + Failure(:division_by_zero, 'arg2 must not be zero') + end + + def divide((number1, number2)) + Success(:division_completed, number1 / number2) + end + end + + test 'method chaining' do + result1 = DivideType.new.call(10, 2) + result2 = DivideTypes.new.call(10, 2) + result3 = DivideTypeAndValue.call(10, 2) + + assert result1.success?(:ok) + assert result2.success?(:division_completed) + assert result3.success?(:division_completed) + end +end diff --git a/test/bcdd/result/addons/given/instance_test.rb b/test/bcdd/result/addons/given/instance_test.rb new file mode 100644 index 0000000..738c5f7 --- /dev/null +++ b/test/bcdd/result/addons/given/instance_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result::AddonsGivenInstanceTest < Minitest::Test + class Divide + include BCDD::Result.mixin(config: { addon: { continue: true } }) + + def call(arg1, arg2) + Given([arg1, arg2]) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers((arg1, arg2)) + arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric') + arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric') + + Continue([arg1, arg2]) + end + + def validate_non_zero(numbers) + return Continue(numbers) unless numbers.last.zero? + + Failure(:division_by_zero, 'arg2 must not be zero') + end + + def divide((number1, number2)) + Success(:division_completed, number1 / number2) + end + end + + test 'method chaining' do + success = Divide.new.call(10, 2) + + failure1 = Divide.new.call('10', 0) + failure2 = Divide.new.call(10, '2') + failure3 = Divide.new.call(10, 0) + + assert_predicate success, :success? + assert_equal :division_completed, success.type + assert_equal 5, success.value + + assert_predicate failure1, :failure? + assert_equal :invalid_arg, failure1.type + assert_equal 'arg1 must be numeric', failure1.value + + assert_predicate failure2, :failure? + assert_equal :invalid_arg, failure2.type + assert_equal 'arg2 must be numeric', failure2.value + + assert_predicate failure3, :failure? + assert_equal :division_by_zero, failure3.type + assert_equal 'arg2 must not be zero', failure3.value + end +end diff --git a/test/bcdd/result/addons/given/singleton_test.rb b/test/bcdd/result/addons/given/singleton_test.rb new file mode 100644 index 0000000..397d585 --- /dev/null +++ b/test/bcdd/result/addons/given/singleton_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result::AddonsGivenSingletonTest < Minitest::Test + module Divide + extend self, BCDD::Result.mixin(config: { addon: { continue: true } }) + + def call(arg1, arg2) + Given([arg1, arg2]) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers((arg1, arg2)) + arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric') + arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric') + + Continue([arg1, arg2]) + end + + def validate_non_zero(numbers) + return Continue(numbers) unless numbers.last.zero? + + Failure(:division_by_zero, 'arg2 must not be zero') + end + + def divide((number1, number2)) + Success(:division_completed, number1 / number2) + end + end + + test 'method chaining using Continue' do + success = Divide.call(10, 2) + + failure1 = Divide.call('10', 0) + failure2 = Divide.call(10, '2') + failure3 = Divide.call(10, 0) + + assert_predicate success, :success? + assert_equal :division_completed, success.type + assert_equal 5, success.value + + assert_predicate failure1, :failure? + assert_equal :invalid_arg, failure1.type + assert_equal 'arg1 must be numeric', failure1.value + + assert_predicate failure2, :failure? + assert_equal :invalid_arg, failure2.type + assert_equal 'arg2 must be numeric', failure2.value + + assert_predicate failure3, :failure? + assert_equal :division_by_zero, failure3.type + assert_equal 'arg2 must not be zero', failure3.value + end +end diff --git a/test/bcdd/result/config/addon/given_test.rb b/test/bcdd/result/config/addon/given_test.rb new file mode 100644 index 0000000..78db5d3 --- /dev/null +++ b/test/bcdd/result/config/addon/given_test.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result::Config + class AddonGivenTest < Minitest::Test + test 'the side effects' do + BCDD::Result.config.addon.disable!(:given) + + class1a = Class.new do + include BCDD::Result.mixin + + def call + Given("this method won't exist as the default config is disabled") + end + end + + class2a = Class.new do + include BCDD::Result::Expectations.mixin + + def call + Given("this method won't exist as the default config is disabled") + end + end + + class3a = Class.new do + include BCDD::Result::Context.mixin + + def call + Given(msg: "this method won't exist as the default config is disabled") + end + end + + class4a = Class.new do + include BCDD::Result::Context::Expectations.mixin + + def call + Given(msg: "this method won't exist as the default config is disabled") + end + end + + error1a = assert_raises(NoMethodError) { class1a.new.call } + error2a = assert_raises(NoMethodError) { class2a.new.call } + error3a = assert_raises(NoMethodError) { class3a.new.call } + error4a = assert_raises(NoMethodError) { class4a.new.call } + + assert_match(/undefined method.+Given/, error1a.message) + assert_match(/undefined method.+Given/, error2a.message) + assert_match(/undefined method.+Given/, error3a.message) + assert_match(/undefined method.+Given/, error4a.message) + + BCDD::Result.config.addon.enable!(:given) + + class1b = Class.new do + include BCDD::Result.mixin + + def call + Given('this method will exist as the config is enabled by default') + end + end + + class2b = Class.new do + include BCDD::Result::Expectations.mixin + + def call + Given('this method will exist as the config is enabled by default') + end + end + + class3b = Class.new do + include BCDD::Result::Context.mixin + + def call + part1 = { part1: 'this method will exist' } + part2 = { part2: 'as the config is enabled by default' } + + Given(part1, part2) + .and_then { |data| Given(msg: "#{data[:part1]} #{data[:part2]}") } + end + end + + class4b = Class.new do + include BCDD::Result::Context::Expectations.mixin + + def call + part1 = { part1: 'this method will exist' } + part2 = { part2: 'as the config is enabled by default' } + + Given(part1, part2) + .and_then { |data| Given(msg: "#{data[:part1]} #{data[:part2]}") } + end + end + + result1b = class1b.new.call + result2b = class2b.new.call + result3b = class3b.new.call + result4b = class4b.new.call + + assert_equal('this method will exist as the config is enabled by default', result1b.value) + assert_equal('this method will exist as the config is enabled by default', result2b.value) + assert_equal('this method will exist as the config is enabled by default', result3b.value[:msg]) + assert_equal('this method will exist as the config is enabled by default', result4b.value[:msg]) + + assert(result1b.success?(:given)) + assert(result2b.success?(:given)) + assert(result3b.success?(:given) && result3b.value.keys == [:msg]) + assert(result4b.success?(:given) && result4b.value.keys == [:msg]) + end + + test 'the overwriting of the default config' do + BCDD::Result.config.addon.enable!(:given) + + class1 = Class.new do + include BCDD::Result.mixin(config: { addon: { given: false } }) + + def call + Given("this method won't exist as the default config was overwritten") + end + end + + class2 = Class.new do + include BCDD::Result::Expectations.mixin(config: { addon: { given: false } }) + + def call + Given("this method won't exist as the default config was overwritten") + end + end + + class3 = Class.new do + include BCDD::Result::Context.mixin(config: { addon: { given: false } }) + + def call + Given(msg: "this method won't exist as the default config was overwritten") + end + end + + class4 = Class.new do + include BCDD::Result::Context::Expectations.mixin(config: { addon: { given: false } }) + + def call + Given(msg: "this method won't exist as the default config was overwritten") + end + end + + error1 = assert_raises(NoMethodError) { class1.new.call } + error2 = assert_raises(NoMethodError) { class2.new.call } + error3 = assert_raises(NoMethodError) { class3.new.call } + error4 = assert_raises(NoMethodError) { class4.new.call } + + assert_match(/undefined method.+Given/, error1.message) + assert_match(/undefined method.+Given/, error2.message) + assert_match(/undefined method.+Given/, error3.message) + assert_match(/undefined method.+Given/, error4.message) + end + end +end diff --git a/test/bcdd/result/config/addon_test.rb b/test/bcdd/result/config/addon_test.rb index 4b28aed..898dee6 100644 --- a/test/bcdd/result/config/addon_test.rb +++ b/test/bcdd/result/config/addon_test.rb @@ -18,7 +18,8 @@ class AddonTest < Minitest::Test assert_equal( { - continue: { enabled: false, affects: AFFECTS } + continue: { enabled: false, affects: AFFECTS }, + given: { enabled: true, affects: AFFECTS } }, config.options ) diff --git a/test/bcdd/result/config_test.rb b/test/bcdd/result/config_test.rb index d5215ce..c6911f1 100644 --- a/test/bcdd/result/config_test.rb +++ b/test/bcdd/result/config_test.rb @@ -47,7 +47,7 @@ class Test < Minitest::Test test '#to_h' do config_values = BCDD::Result.config.to_h - assert_equal({ continue: false }, config_values[:addon]) + assert_equal({ continue: false, given: true }, config_values[:addon]) assert_equal({ expectations: true, transitions: true }, config_values[:feature]) assert_equal({ nil_as_valid_value_checking: false }, config_values[:pattern_matching]) assert_equal({ 'Result' => false, 'Context' => false, 'BCDD::Context' => false }, config_values[:constant_alias]) diff --git a/test/bcdd/result/context/addons/given/expectations_test.rb b/test/bcdd/result/context/addons/given/expectations_test.rb new file mode 100644 index 0000000..6a025aa --- /dev/null +++ b/test/bcdd/result/context/addons/given/expectations_test.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result::Context::AddonsGivenExpectationsTest < Minitest::Test + class DivideType + include BCDD::Result::Context::Expectations.mixin( + config: { addon: { continue: true } }, + success: :ok, + failure: :err + ) + + def call(arg1, arg2) + Given(number1: arg1, number2: arg2) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers(number1:, number2:) + number1.is_a?(::Numeric) or return Failure(:err, message: 'arg1 must be numeric') + number2.is_a?(::Numeric) or return Failure(:err, message: 'arg2 must be numeric') + + Continue() + end + + def validate_non_zero(number2:, **) + number2.zero? ? Failure(:err, 'arg2 must not be zero') : Continue() + end + + def divide(number1:, number2:) + Success(:ok, number: number1 / number2) + end + end + + class DivideTypes + include BCDD::Result::Context::Expectations.mixin( + config: { addon: { continue: true } }, + success: :division_completed, + failure: %i[invalid_arg division_by_zero] + ) + + def call(arg1, arg2) + Given(number1: arg1, number2: arg2) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers(number1:, number2:) + number1.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric') + number2.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric') + + Continue() + end + + def validate_non_zero(number2:, **) + number2.zero? ? Failure(:err, message: 'arg2 must not be zero') : Continue() + end + + def divide(number1:, number2:) + Success(:division_completed, number: number1 / number2) + end + end + + module DivideTypeAndValue + extend self, BCDD::Result::Context::Expectations.mixin( + config: { addon: { continue: true } }, + success: { + division_completed: ->(value) { + case value + in { number: Numeric } then true + end + } + }, + failure: { + invalid_arg: ->(value) { + case value + in { message: String } then true + end + }, + division_by_zero: ->(value) { + case value + in { message: String } then true + end + } + } + ) + + def call(arg1, arg2) + Given(number1: arg1, number2: arg2) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers(number1:, number2:) + number1.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric') + number2.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric') + + Continue() + end + + def validate_non_zero(number2:, **) + return Failure(:division_by_zero, message: 'arg2 must not be zero') if number2.zero? + + Continue() + end + + def divide(number1:, number2:) + Success(:division_completed, number: number1 / number2) + end + end + + test 'method chaining' do + result1 = DivideType.new.call(10, 2) + result2 = DivideTypes.new.call(10, 2) + result3 = DivideTypeAndValue.call(10, 2) + + assert result1.success?(:ok) + assert result2.success?(:division_completed) + assert result3.success?(:division_completed) + end +end diff --git a/test/bcdd/result/context/addons/given/instance_test.rb b/test/bcdd/result/context/addons/given/instance_test.rb new file mode 100644 index 0000000..dba2a13 --- /dev/null +++ b/test/bcdd/result/context/addons/given/instance_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result::Context::AddonsGivenInstanceTest < Minitest::Test + class Divide + include BCDD::Result::Context.mixin(config: { addon: { continue: true } }) + + def call(arg1, arg2) + Given(number1: arg1, number2: arg2) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide) + end + + private + + def validate_numbers(number1:, number2:) + number1.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric') + number2.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric') + + Continue() + end + + def validate_non_zero(number2:, **) + return Continue() unless number2.zero? + + Failure(:division_by_zero, message: 'arg2 must not be zero') + end + + def divide(number1:, number2:) + Success(:division_completed, number: number1 / number2) + end + end + + test 'method chaining using Continue' do + success = Divide.new.call(10, 2) + + failure1 = Divide.new.call('10', 0) + failure2 = Divide.new.call(10, '2') + failure3 = Divide.new.call(10, 0) + + assert_predicate success, :success? + assert_equal :division_completed, success.type + assert_equal({ number: 5 }, success.value) + + assert_predicate failure1, :failure? + assert_equal :invalid_arg, failure1.type + assert_equal({ message: 'arg1 must be numeric' }, failure1.value) + + assert_predicate failure2, :failure? + assert_equal :invalid_arg, failure2.type + assert_equal({ message: 'arg2 must be numeric' }, failure2.value) + + assert_predicate failure3, :failure? + assert_equal :division_by_zero, failure3.type + assert_equal({ message: 'arg2 must not be zero' }, failure3.value) + end +end diff --git a/test/bcdd/result/context/addons/given/singleton_test.rb b/test/bcdd/result/context/addons/given/singleton_test.rb new file mode 100644 index 0000000..e6a6712 --- /dev/null +++ b/test/bcdd/result/context/addons/given/singleton_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'test_helper' + +class BCDD::Result::Context::AddonsGivenSingletonTest < Minitest::Test + module Divide + extend self, BCDD::Result::Context.mixin(config: { addon: { continue: true } }) + + def call(arg1, arg2) + Given(number1: arg1, number2: arg2) + .and_then(:validate_numbers) + .and_then(:validate_non_zero) + .and_then(:divide, extra_division: 2) + end + + private + + def validate_numbers(number1:, number2:) + number1.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric') + number2.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric') + + Continue() + end + + def validate_non_zero(number2:, **) + return Continue() unless number2.zero? + + Failure(:division_by_zero, message: 'arg2 must not be zero') + end + + def divide(number1:, number2:, extra_division:) + Success(:division_completed, number: (number1 / number2) / extra_division) + end + end + + test 'method chaining' do + success = Divide.call(20, 2) + + failure1 = Divide.call('10', 0) + failure2 = Divide.call(10, '2') + failure3 = Divide.call(10, 0) + + assert_predicate success, :success? + assert_equal :division_completed, success.type + assert_equal({ number: 5 }, success.value) + + assert_predicate failure1, :failure? + assert_equal :invalid_arg, failure1.type + assert_equal({ message: 'arg1 must be numeric' }, failure1.value) + + assert_predicate failure2, :failure? + assert_equal :invalid_arg, failure2.type + assert_equal({ message: 'arg2 must be numeric' }, failure2.value) + + assert_predicate failure3, :failure? + assert_equal :division_by_zero, failure3.type + assert_equal({ message: 'arg2 must not be zero' }, failure3.value) + end +end diff --git a/test/bcdd/result/transitions/enabled/with_subject/singleton/nested_test.rb b/test/bcdd/result/transitions/enabled/with_subject/singleton/nested_test.rb index ea366ad..64a8058 100644 --- a/test/bcdd/result/transitions/enabled/with_subject/singleton/nested_test.rb +++ b/test/bcdd/result/transitions/enabled/with_subject/singleton/nested_test.rb @@ -5,14 +5,8 @@ class BCDD::Result::TransitionsEnabledWithSubjectSingletonNestedTest < Minitest::Test include BCDDResultTransitionAssertions - module Giveable - def Given(value) - _ResultAs(BCDD::Result::Success, :initial_input, value) - end - end - module CheckForZeros - extend self, Giveable, BCDD::Result.mixin(config: { addon: { continue: true } }) + extend self, BCDD::Result.mixin(config: { addon: { continue: true } }) def call(numbers) BCDD::Result.transitions(name: 'CheckForZeros') do @@ -33,7 +27,7 @@ def detect_zero(numbers, ref) end module Division - extend self, Giveable, BCDD::Result.mixin(config: { addon: { continue: true } }) + extend self, BCDD::Result.mixin(config: { addon: { continue: true } }) def call(num1, num2) BCDD::Result.transitions(name: 'Division') do @@ -67,7 +61,7 @@ def divide((num1, num2)) end module SumDivisionsByTwo - extend self, Giveable, BCDD::Result.mixin(config: { addon: { continue: true } }) + extend self, BCDD::Result.mixin(config: { addon: { continue: true } }) def call(*numbers) BCDD::Result.transitions(name: 'SumDivisionsByTwo') do @@ -105,21 +99,49 @@ def sum_divisions(divisions) assert_transitions(result3, size: 21) assert_transitions(result4, size: 27) + { + root: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + parent: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + current: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + result: { kind: :success, type: :given, value: %w[30 20 10] } + }.then { |spec| assert_transition_record(result1, 0, spec) } + assert_equal( [0, [[1, []], [2, []], [3, []]]], result1.transitions[:metadata][:tree_map] ) + { + root: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + parent: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + current: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + result: { kind: :success, type: :given, value: [30, '20', '10'] } + }.then { |spec| assert_transition_record(result2, 0, spec) } + assert_equal( [0, [[1, [[2, []]]], [3, []], [4, []]]], result2.transitions[:metadata][:tree_map] ) + { + root: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + parent: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + current: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + result: { kind: :success, type: :given, value: [30, 20, '10'] } + }.then { |spec| assert_transition_record(result3, 0, spec) } + assert_equal( [0, [[1, [[2, []]]], [3, [[4, []]]], [5, []]]], result3.transitions[:metadata][:tree_map] ) + { + root: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + parent: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + current: { id: 0, name: 'SumDivisionsByTwo', desc: nil }, + result: { kind: :success, type: :given, value: [30, 20, 10] } + }.then { |spec| assert_transition_record(result4, 0, spec) } + assert_equal( [0, [[1, [[2, []]]], [3, [[4, []]]], [5, [[6, []]]]]], result4.transitions[:metadata][:tree_map] From b8ff65ceac064a1d8b8cf3be453802b82be990c8 Mon Sep 17 00:00:00 2001 From: Rodrigo Serradura Date: Mon, 1 Jan 2024 23:50:44 -0300 Subject: [PATCH 6/7] Update CHANGELOG.md --- CHANGELOG.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 332d891..1b9b6c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,39 +1,44 @@ - [\[Unreleased\]](#unreleased) + - [Added](#added) - [Changed](#changed) - [\[0.10.0\] - 2023-12-31](#0100---2023-12-31) - - [Added](#added) + - [Added](#added-1) - [\[0.9.1\] - 2023-12-12](#091---2023-12-12) - [Changed](#changed-1) - [Fixed](#fixed) - [\[0.9.0\] - 2023-12-12](#090---2023-12-12) - - [Added](#added-1) + - [Added](#added-2) - [Changed](#changed-2) - [\[0.8.0\] - 2023-12-11](#080---2023-12-11) - - [Added](#added-2) + - [Added](#added-3) - [Changed](#changed-3) - [Removed](#removed) - [\[0.7.0\] - 2023-10-27](#070---2023-10-27) - - [Added](#added-3) + - [Added](#added-4) - [Changed](#changed-4) - [\[0.6.0\] - 2023-10-11](#060---2023-10-11) - - [Added](#added-4) + - [Added](#added-5) - [Changed](#changed-5) - [\[0.5.0\] - 2023-10-09](#050---2023-10-09) - - [Added](#added-5) -- [\[0.4.0\] - 2023-09-28](#040---2023-09-28) - [Added](#added-6) +- [\[0.4.0\] - 2023-09-28](#040---2023-09-28) + - [Added](#added-7) - [Changed](#changed-6) - [Removed](#removed-1) - [\[0.3.0\] - 2023-09-26](#030---2023-09-26) - - [Added](#added-7) -- [\[0.2.0\] - 2023-09-26](#020---2023-09-26) - [Added](#added-8) +- [\[0.2.0\] - 2023-09-26](#020---2023-09-26) + - [Added](#added-9) - [Removed](#removed-2) - [\[0.1.0\] - 2023-09-25](#010---2023-09-25) - - [Added](#added-9) + - [Added](#added-10) ## [Unreleased] +### Added + +- Add the `Given()` addon to produce a `Success(:given, value)` result. As the `Continue()` addon, it is ignored by the expectations. Use it to add a value to the result chain and invoke the next step (through `and_then`). + ### Changed - **(BREAKING)** Rename halted concept to terminal. Failures are terminal by default, but you can make a success terminal by enabling the `:continue` addon. From b4e66cac7fd3dcff3988b0102a806f4e164db860 Mon Sep 17 00:00:00 2001 From: Rodrigo Serradura Date: Tue, 2 Jan 2024 00:38:20 -0300 Subject: [PATCH 7/7] Update README.md --- README.md | 156 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index d5304a9..167a01a 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi - [`BCDD::Result.transitions`](#bcddresulttransitions) - [Configuration](#configuration) - [`BCDD::Result.configuration`](#bcddresultconfiguration) - - [`config.addon.enable!(:continue)`](#configaddonenablecontinue) + - [`config.addon.enable!(:given, :continue)`](#configaddonenablegiven-continue) - [`config.constant_alias.enable!('Result', 'BCDD::Context')`](#configconstant_aliasenableresult-bcddcontext) - [`config.pattern_matching.disable!(:nil_as_valid_value_checking)`](#configpattern_matchingdisablenil_as_valid_value_checking) - [`config.feature.disable!(:expectations)`](#configfeaturedisableexpectations) @@ -918,35 +918,48 @@ Divide.call(4, 2, logger: Logger.new(IO::NULL)) The `BCDD::Result.mixin` also accepts the `config:` argument. It is a hash that will be used to define custom behaviors for the mixin. +**given** + +This addon is enabled by default. It will create the `Given(value)` method. Use it to add a value to the result chain and invoke the next step (through `and_then`). + +You can turn it off by passing `given: false` to the `config:` argument or using the `BCDD::Result.configuration`. + **continue** This addon will create the `Continue(value)` method and change the `Success()` behavior to terminate the step chain. So, if you want to advance to the next step, you must use `Continue(value)` instead of `Success(type, value)`. Otherwise, the step chain will be terminated. +In this example below, the `validate_nonzero` will return a `Success(:division_completed, 0)` and terminate the chain if the first number is zero. + ```ruby module Divide extend self, BCDD::Result.mixin(config: { addon: { continue: true } }) def call(arg1, arg2) - validate_numbers(arg1, arg2) + Given([arg1, arg2]) + .and_then(:validate_numbers) .and_then(:validate_nonzero) .and_then(:divide) end private - def validate_numbers(arg1, arg2) - arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric') - arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric') + def validate_numbers(numbers) + number1, number2 = numbers - Continue([arg1, arg2]) + number1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric') + number2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric') + + Continue(numbers) end def validate_nonzero(numbers) - return Continue(numbers) unless numbers.last.zero? + return Failure(:division_by_zero, 'arg2 must not be zero') if numbers.last.zero? - Failure(:division_by_zero, 'arg2 must not be zero') + return Success(:division_completed, 0) if numbers.first.zero? + + Continue(numbers) end def divide((number1, number2)) @@ -1678,7 +1691,15 @@ Divide::Result::Success(:division_completed, number: '2') The `BCDD::Result::Context.mixin` and `BCDD::Result::Context::Expectations.mixin` also accepts the `config:` argument. And it works the same way as the `BCDD::Result` mixins. -**Continue** +**given** + +This addon is enabled by default. It will create the `Given(*value)` method. Use it to add a value to the result chain and invoke the next step (through `and_then`). + +You can turn it off by passing `given: false` to the `config:` argument or using the `BCDD::Result.configuration`. + +The `Given()` addon for a BCDD::Result::Context can be called with one or more arguments. The arguments will be converted to a hash (`to_h`) and merged to define the first value of the result chain. + +**continue** The `BCDD::Result::Context.mixin(config: { addon: { continue: true } })` or `BCDD::Result::Context::Expectations.mixin(config: { addon: { continue: true } })` creates the `Continue(value)` method and change the `Success()` behavior to terminate the step chain. @@ -1687,7 +1708,7 @@ So, if you want to advance to the next step, you must use `Continue(**value)` in Let's use a mix of `BCDD::Result::Context` features to see in action with this add-on: ```ruby -module Divide +module Division require 'logger' extend self, BCDD::Result::Context::Expectations.mixin( @@ -1705,25 +1726,28 @@ module Divide ) def call(arg1, arg2, logger: ::Logger.new(STDOUT)) - validate_numbers(arg1, arg2) - .and_then(:validate_nonzero) + Given(number1: arg1, number2: arg2) + .and_then(:require_numbers) + .and_then(:check_for_zeros) .and_then(:divide, logger: logger) .and_expose(:division_completed, [:number]) end private - def validate_numbers(arg1, arg2) - arg1.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric') - arg2.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric') + def require_numbers(number1:, number2:) + number1.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric') + number2.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric') - Continue(number1: arg1, number2: arg2) + Continue() end - def validate_nonzero(number2:, **) - return Continue() if number2.nonzero? + def check_for_zeros(number1:, number2:) + return Failure(:division_by_zero, message: 'arg2 must not be zero') if number2.zero? + + return Success(:division_completed, number: 0) if number1.zero? - Failure(:division_by_zero, message: 'arg2 must not be zero') + Continue() end def divide(number1:, number2:, logger:) @@ -1735,23 +1759,26 @@ module Divide end end -Divide.call(14, 2) +Division.call(14, 2) # I, [2023-10-27T02:01:05.812388 #77823] INFO -- : The division result is 7 #7}> -Divide.call('14', 2) +Division.call(0, 2) +##0}> + +Division.call('14', 2) #"arg1 must be numeric"}> -Divide.call(14, '2') +Division.call(14, '2') #"arg2 must be numeric"}> -Divide.call(14, 0) +Division.call(14, 0) #"arg2 must not be zero"}> ``` ### `BCDD::Result.transitions` -Use `BCDD::Result.transitions(&block)` to track all transitions in the same or between different operations. When there is a nesting of transition blocks, this mechanism will be able to correlate parent and child blocks and present the duration of all operations in milliseconds. +Use `BCDD::Result.transitions(&block)` to track all transitions in the same or between different operations (it works with `BCDD::Result` and `BCDD::Result::Context`). When there is a nesting of transition blocks, this mechanism will be able to correlate parent and child blocks and present the duration of all operations in milliseconds. When you wrap the creation of the result with `BCDD::Result.transitions`, the final result will expose all the transition records through the `BCDD::Result#transitions` method. @@ -1761,7 +1788,8 @@ class Division def call(arg1, arg2) BCDD::Result.transitions(name: 'Division', desc: 'divide two numbers') do - require_numbers(arg1, arg2) + Given([arg1, arg2]) + .and_then(:require_numbers) .and_then(:check_for_zeros) .and_then(:divide) end @@ -1771,7 +1799,7 @@ class Division ValidNumber = ->(arg) { arg.is_a?(Numeric) && arg != Float::NAN && arg != Float::INFINITY } - def require_numbers(arg1, arg2) + def require_numbers((arg1, arg2)) ValidNumber[arg1] or return Failure(:invalid_arg, 'arg1 must be a valid number') ValidNumber[arg2] or return Failure(:invalid_arg, 'arg2 must be a valid number') @@ -1823,52 +1851,68 @@ result.transitions }, :records => [ { - :root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, - :parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, - :current => {:id=>1, :name=>"Division", :desc=>"divide two numbers"}, - :result => {:kind=>:success, :type=>:continued, :value=>[20, 2]}, - :and_then => {}, - :time => 2023-12-31 22:19:33.281619 UTC + :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, + :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, + :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"}, + :result=>{:kind=>:success, :type=>:given, :value=>[20, 2]}, + :and_then=>{}, + :time=>2024-01-02 03:35:11.248418 UTC }, { :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"}, :result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]}, - :and_then=>{:type=>:method, :arg=>nil, :subject=>#, :method_name=>:check_for_zeros}, - :time=>2023-12-31 22:19:33.281693 UTC + :and_then=>{:type=>:method, :arg=>nil, :subject=>, :method_name=>:require_numbers}, + :time=>2024-01-02 03:35:11.248558 UTC + }, + { + :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, + :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, + :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"}, + :result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]}, + :and_then=>{:type=>:method, :arg=>nil, :subject=>, :method_name=>:check_for_zeros}, + :time=>2024-01-02 03:35:11.248587 UTC }, { :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"}, :result=>{:kind=>:success, :type=>:division_completed, :value=>10}, - :and_then=>{:type=>:method, :arg=>nil, :subject=>#, :method_name=>:divide}, - :time=>2023-12-31 22:19:33.281715 UTC + :and_then=>{:type=>:method, :arg=>nil, :subject=>, :method_name=>:divide}, + :time=>2024-01-02 03:35:11.248607 UTC }, { :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"}, - :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]}, + :result=>{:kind=>:success, :type=>:given, :value=>[10, 2]}, :and_then=>{}, - :time=>2023-12-31 22:19:33.281747 UTC + :time=>2024-01-02 03:35:11.24865 UTC }, { :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"}, :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]}, - :and_then=>{:type=>:method, :arg=>nil, :subject=>#, :method_name=>:check_for_zeros}, - :time=>2023-12-31 22:19:33.281755 UTC + :and_then=>{:type=>:method, :arg=>nil, :subject=>, :method_name=>:require_numbers}, + :time=>2024-01-02 03:35:11.248661 UTC + }, + { + :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, + :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, + :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"}, + :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]}, + :and_then=>{:type=>:method, :arg=>nil, :subject=>, :method_name=>:check_for_zeros}, + :time=>2024-01-02 03:35:11.248672 UTC }, { :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"}, :result=>{:kind=>:success, :type=>:division_completed, :value=>5}, - :and_then=>{:type=>:method, :arg=>nil, :subject=>#, :method_name=>:divide}, - :time=>2023-12-31 22:19:33.281763 UTC + :and_then=>{:type=>:method, :arg=>nil, :subject=>, :method_name=>:divide}, + :time=>2024-01-02 03:35:11.248682 UTC }, { :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, @@ -1876,7 +1920,7 @@ result.transitions :current=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil}, :result=>{:kind=>:success, :type=>:sum, :value=>15}, :and_then=>{}, - :time=>2023-12-31 22:19:33.281784 UTC + :time=>2024-01-02 03:35:11.248721 UTC } ] } @@ -1909,7 +1953,7 @@ The `BCDD::Result.configuration` allows you to configure default behaviors for ` ```ruby BCDD::Result.configuration do |config| - config.addon.enable!(:continue) + config.addon.enable!(:given, :continue) config.constant_alias.enable!('Result', 'BCDD::Context') @@ -1923,9 +1967,11 @@ Use `disable!` to disable a feature and `enable!` to enable it. Let's see what each configuration in the example above does: -#### `config.addon.enable!(:continue)` +#### `config.addon.enable!(:given, :continue)` + +This configuration enables the `Continue()` method for `BCDD::Result.mixin`, `BCDD::Result::Context.mixin`, `BCDD::Result::Expectation.mixin`, and `BCDD::Result::Context::Expectation.mixin`. Link to documentations: [(1)](#add-ons) [(2)](#mixin-add-ons). -This configuration enables the `Continue()` method for `BCDD::Result`, `BCDD::Result::Context`, `BCDD::Result::Expectation`, and `BCDD::Result::Context::Expectation`. Link to documentations: [(1)](#add-ons) [(2)](#mixin-add-ons). +It is also enabling the `Given()` which is already enabled by default. Link to documentation: [(1)](#add-ons) [(2)](#mixin-add-ons). #### `config.constant_alias.enable!('Result', 'BCDD::Context')` @@ -1955,16 +2001,26 @@ The `BCDD::Result.config` allows you to access the current configuration. ```ruby BCDD::Result.config.addon.enabled?(:continue) +BCDD::Result.config.addon.enabled?(:given) BCDD::Result.config.addon.options # { # :continue=>{ # :enabled=>false, # :affects=>[ -# "BCDD::Result", -# "BCDD::Result::Context", -# "BCDD::Result::Expectations", -# "BCDD::Result::Context::Expectations" +# "BCDD::Result.mixin", +# "BCDD::Result::Context.mixin", +# "BCDD::Result::Expectations.mixin", +# "BCDD::Result::Context::Expectations.mixin" +# ] +# }, +# :given=>{ +# :enabled=>true, +# :affects=>[ +# "BCDD::Result.mixin", +# "BCDD::Result::Context.mixin", +# "BCDD::Result::Expectations.mixin", +# "BCDD::Result::Context::Expectations.mixin" # ] # } # }