From c38dc020b628b0ecd0d7a1ced3aec9600cd9e09b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kondas Date: Sun, 23 Dec 2018 22:11:45 +0100 Subject: [PATCH] Add phpspecs as spec for better tests :rocket: --- .php_cs | 3 +- composer.json | 5 +- extension.neon | 6 + .../MagicAwareAccessInspectorSpec.php | 99 ++++ .../StaticRejectingNamespaceResolverSpec.php | 45 ++ .../TokenizedNamespaceResolverSpec.php | 128 ++++ .../TokenizedTypeHintRewriterSpec.php | 260 ++++++++ .../VisibilityAccessInspectorSpec.php | 80 +++ .../Generator/ClassGeneratorSpec.php | 154 +++++ .../Generator/ConfirmingGeneratorSpec.php | 59 ++ .../Generator/MethodGeneratorSpec.php | 84 +++ .../NamedConstructorGeneratorSpec.php | 87 +++ .../NewFileNotifyingGeneratorSpec.php | 121 ++++ .../Generator/OneTimeGeneratorSpec.php | 56 ++ .../Generator/ReturnConstantGeneratorSpec.php | 39 ++ .../Generator/SpecificationGeneratorSpec.php | 138 +++++ ...ateClassNameSpecificationGeneratorSpec.php | 74 +++ .../CodeGenerator/GeneratorManagerSpec.php | 50 ++ .../CodeGenerator/TemplateRendererSpec.php | 99 ++++ .../Writer/TokenizedCodeWriterSpec.php | 290 +++++++++ spec/PhpSpec/Config/OptionsConfigSpec.php | 66 +++ spec/PhpSpec/Console/ApplicationSpec.php | 20 + spec/PhpSpec/Console/ConsoleIOSpec.php | 283 +++++++++ .../NamespacesAutocompleteProviderSpec.php | 50 ++ spec/PhpSpec/Console/ResultConverterSpec.php | 36 ++ spec/PhpSpec/Event/ExampleEventSpec.php | 74 +++ spec/PhpSpec/Event/ExpectationEventSpec.php | 93 +++ spec/PhpSpec/Event/FileCreationEventSpec.php | 32 + spec/PhpSpec/Event/MethodCallEventSpec.php | 66 +++ spec/PhpSpec/Event/SpecificationEventSpec.php | 61 ++ spec/PhpSpec/Event/SuiteEventSpec.php | 72 +++ spec/PhpSpec/Exception/ErrorExceptionSpec.php | 42 ++ .../Example/NotEqualExceptionSpec.php | 30 + .../Example/StopOnFailureExceptionSpec.php | 25 + .../Exception/ExceptionFactorySpec.php | 174 ++++++ spec/PhpSpec/Exception/ExceptionSpec.php | 23 + .../Fracture/ClassNotFoundExceptionSpec.php | 25 + .../InterfaceNotImplementedExceptionSpec.php | 30 + .../Fracture/MethodNotFoundExceptionSpec.php | 35 ++ .../MethodNotVisibleExceptionSpec.php | 35 ++ .../NamedConstructorNotFoundExceptionSpec.php | 35 ++ .../PropertyNotFoundExceptionSpec.php | 30 + .../InvalidCollaboratorTypeExceptionSpec.php | 40 ++ spec/PhpSpec/Formatter/BasicFormatterSpec.php | 44 ++ spec/PhpSpec/Formatter/DotFormatterSpec.php | 205 +++++++ spec/PhpSpec/Formatter/Html/HtmlIOSpec.php | 17 + .../Formatter/Html/ReportFailedItemSpec.php | 47 ++ .../Formatter/Html/ReportItemFactorySpec.php | 43 ++ .../Formatter/Html/ReportPassedItemSpec.php | 30 + .../Formatter/Html/ReportPendingItemSpec.php | 31 + spec/PhpSpec/Formatter/Html/TemplateSpec.php | 58 ++ spec/PhpSpec/Formatter/HtmlFormatterSpec.php | 41 ++ spec/PhpSpec/Formatter/JUnitFormatterSpec.php | 227 +++++++ .../Presenter/Differ/ArrayEngineSpec.php | 25 + .../Formatter/Presenter/Differ/DifferSpec.php | 44 ++ .../Presenter/Differ/ObjectEngineSpec.php | 45 ++ .../Presenter/Differ/StringEngineSpec.php | 48 ++ .../Exception/CallArgumentsPresenterSpec.php | 73 +++ .../GenericPhpSpecExceptionPresenterSpec.php | 21 + .../HtmlPhpSpecExceptionPresenterSpec.php | 15 + .../SimpleExceptionElementPresenterSpec.php | 81 +++ .../SimpleExceptionPresenterSpec.php | 33 ++ .../TaggingExceptionElementPresenterSpec.php | 67 +++ .../Presenter/SimplePresenterSpec.php | 55 ++ .../Presenter/TaggingPresenterSpec.php | 40 ++ .../Value/ArrayTypePresenterSpec.php | 30 + .../Value/BaseExceptionTypePresenterSpec.php | 33 ++ .../Value/BooleanTypePresenterSpec.php | 31 + .../Value/CallableTypePresenterSpec.php | 117 ++++ .../Value/ComposedValuePresenterSpec.php | 98 +++ .../Presenter/Value/NullTypePresenterSpec.php | 25 + .../Value/ObjectTypePresenterSpec.php | 25 + .../Value/QuotingStringTypePresenterSpec.php | 27 + .../TruncatingStringTypePresenterSpec.php | 48 ++ .../Formatter/ProgressFormatterSpec.php | 117 ++++ spec/PhpSpec/Formatter/TapFormatterSpec.php | 149 +++++ .../Listener/ClassNotFoundListenerSpec.php | 94 +++ ...CollaboratorMethodNotFoundListenerSpec.php | 231 ++++++++ .../CollaboratorNotFoundListenerSpec.php | 150 +++++ .../Listener/CurrentExampleListenerSpec.php | 58 ++ .../Listener/MethodNotFoundListenerSpec.php | 129 ++++ .../MethodReturnedNullListenerSpec.php | 272 +++++++++ .../NamedConstructorNotFoundListenerSpec.php | 93 +++ spec/PhpSpec/Listener/RerunListenerSpec.php | 42 ++ .../Listener/StatisticsCollectorSpec.php | 114 ++++ .../Listener/StopOnFailureListenerSpec.php | 68 +++ spec/PhpSpec/Loader/Node/ExampleNodeSpec.php | 81 +++ .../Loader/Node/SpecificationNodeSpec.php | 65 ++ spec/PhpSpec/Loader/SuiteSpec.php | 38 ++ .../Transformer/InMemoryTypeHintIndexSpec.php | 44 ++ .../Transformer/TypeHintRewriterSpec.php | 28 + spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php | 558 ++++++++++++++++++ .../PhpSpec/Locator/PSR0/PSR0ResourceSpec.php | 120 ++++ .../PrioritizedResourceManagerSpec.php | 136 +++++ .../Matcher/ApproximatelyMatcherSpec.php | 46 ++ .../Matcher/ArrayContainMatcherSpec.php | 48 ++ .../PhpSpec/Matcher/ArrayCountMatcherSpec.php | 72 +++ spec/PhpSpec/Matcher/ArrayKeyMatcherSpec.php | 74 +++ .../Matcher/ArrayKeyValueMatcherSpec.php | 114 ++++ spec/PhpSpec/Matcher/CallbackMatcherSpec.php | 55 ++ .../PhpSpec/Matcher/ComparisonMatcherSpec.php | 108 ++++ spec/PhpSpec/Matcher/IdentityMatcherSpec.php | 123 ++++ .../Matcher/Iterate/IterablesMatcherSpec.php | 147 +++++ ...ubjectElementDoesNotMatchExceptionSpec.php | 26 + .../SubjectHasFewerElementsExceptionSpec.php | 20 + .../SubjectHasMoreElementsExceptionSpec.php | 20 + spec/PhpSpec/Matcher/IterateAsMatcherSpec.php | 159 +++++ .../Matcher/IterateLikeMatcherSpec.php | 92 +++ .../Matcher/ObjectStateMatcherSpec.php | 121 ++++ spec/PhpSpec/Matcher/ScalarMatcherSpec.php | 478 +++++++++++++++ .../Matcher/StartIteratingAsMatcherSpec.php | 165 ++++++ .../Matcher/StringContainMatcherSpec.php | 75 +++ spec/PhpSpec/Matcher/StringEndMatcherSpec.php | 55 ++ .../Matcher/StringRegexMatcherSpec.php | 55 ++ .../Matcher/StringStartMatcherSpec.php | 55 ++ spec/PhpSpec/Matcher/ThrowMatcherSpec.php | 83 +++ .../Matcher/TraversableContainMatcherSpec.php | 75 +++ .../Matcher/TraversableCountMatcherSpec.php | 107 ++++ .../Matcher/TraversableKeyMatcherSpec.php | 75 +++ .../TraversableKeyValueMatcherSpec.php | 85 +++ spec/PhpSpec/Matcher/TriggerMatcherSpec.php | 53 ++ spec/PhpSpec/Matcher/TypeMatcherSpec.php | 81 +++ .../Message/CurrentExampleTrackerSpec.php | 26 + .../ComposerPsrNamespaceProviderSpec.php | 53 ++ .../Context/JsonExecutionContextSpec.php | 39 ++ .../Prerequisites/SuitePrerequisitesSpec.php | 30 + .../ReRunner/CompositeReRunnerSpec.php | 61 ++ .../Process/ReRunner/OptionalReRunnerSpec.php | 35 ++ .../Process/ReRunner/PcntlReRunnerSpec.php | 29 + .../Process/ReRunner/ProcOpenReRunnerSpec.php | 40 ++ .../ReRunner/WindowsPassthruReRunnerSpec.php | 40 ++ .../PhpSpec/Process/Shutdown/ShutdownSpec.php | 23 + .../Shutdown/UpdateConsoleActionSpec.php | 27 + .../Runner/CollaboratorManagerSpec.php | 74 +++ spec/PhpSpec/Runner/ExampleRunnerSpec.php | 175 ++++++ .../Runner/Maintainer/ErrorMaintainerSpec.php | 41 ++ .../Maintainer/MatchersMaintainerSpec.php | 30 + spec/PhpSpec/Runner/MatcherManagerSpec.php | 51 ++ .../Runner/SpecificationRunnerSpec.php | 76 +++ spec/PhpSpec/Runner/SuiteRunnerSpec.php | 113 ++++ .../IndexedServiceContainerSpec.php | 121 ++++ .../Specification/ErrorSpecificationSpec.php | 16 + spec/PhpSpec/Util/ClassFileAnalyserSpec.php | 97 +++ spec/PhpSpec/Util/ClassNameCheckerSpec.php | 41 ++ spec/PhpSpec/Util/ExampleObjectUsingTrait.php | 10 + spec/PhpSpec/Util/ExampleTrait.php | 20 + spec/PhpSpec/Util/InstantiatorSpec.php | 57 ++ spec/PhpSpec/Util/MethodAnalyserSpec.php | 102 ++++ .../ReservedWordsMethodNameCheckerSpec.php | 25 + spec/PhpSpec/Wrapper/Subject/CallerSpec.php | 298 ++++++++++ .../Expectation/ConstructorDecoratorSpec.php | 38 ++ .../Subject/Expectation/DecoratorSpec.php | 40 ++ .../Expectation/DispatcherDecoratorSpec.php | 67 +++ .../Subject/Expectation/NegativeSpec.php | 27 + .../Subject/Expectation/PositiveSpec.php | 26 + .../Subject/ExpectationFactorySpec.php | 84 +++ .../Wrapper/Subject/WrappedObjectSpec.php | 102 ++++ spec/PhpSpec/Wrapper/SubjectSpec.php | 78 +++ spec/Proget/Tests/PHPStan/PhpSpec/BarSpec.php | 43 -- spec/Proget/Tests/PHPStan/PhpSpec/FooSpec.php | 44 -- src/Locator/SpecClassLocator.php | 2 +- .../ObjectBehaviorMethodReflection.php | 62 ++ ...ehaviorMethodsClassReflectionExtension.php | 31 +- ...viorPropertiesClassReflectionExtension.php | 70 +++ ...oratorDynamicMethodReturnTypeExtension.php | 2 +- ...haviorDynamicMethodReturnTypeExtension.php | 36 ++ tests/Bar.php | 32 - tests/Baz.php | 10 - tests/Foo.php | 37 -- tests/ValueObject.php | 28 - 170 files changed, 12615 insertions(+), 226 deletions(-) create mode 100644 spec/PhpSpec/CodeAnalysis/MagicAwareAccessInspectorSpec.php create mode 100644 spec/PhpSpec/CodeAnalysis/StaticRejectingNamespaceResolverSpec.php create mode 100644 spec/PhpSpec/CodeAnalysis/TokenizedNamespaceResolverSpec.php create mode 100644 spec/PhpSpec/CodeAnalysis/TokenizedTypeHintRewriterSpec.php create mode 100644 spec/PhpSpec/CodeAnalysis/VisibilityAccessInspectorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/ClassGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/ConfirmingGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/MethodGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/NamedConstructorGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/NewFileNotifyingGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/OneTimeGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/ReturnConstantGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/SpecificationGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Generator/ValidateClassNameSpecificationGeneratorSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/GeneratorManagerSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/TemplateRendererSpec.php create mode 100644 spec/PhpSpec/CodeGenerator/Writer/TokenizedCodeWriterSpec.php create mode 100644 spec/PhpSpec/Config/OptionsConfigSpec.php create mode 100644 spec/PhpSpec/Console/ApplicationSpec.php create mode 100644 spec/PhpSpec/Console/ConsoleIOSpec.php create mode 100644 spec/PhpSpec/Console/Provider/NamespacesAutocompleteProviderSpec.php create mode 100644 spec/PhpSpec/Console/ResultConverterSpec.php create mode 100644 spec/PhpSpec/Event/ExampleEventSpec.php create mode 100644 spec/PhpSpec/Event/ExpectationEventSpec.php create mode 100644 spec/PhpSpec/Event/FileCreationEventSpec.php create mode 100644 spec/PhpSpec/Event/MethodCallEventSpec.php create mode 100644 spec/PhpSpec/Event/SpecificationEventSpec.php create mode 100644 spec/PhpSpec/Event/SuiteEventSpec.php create mode 100644 spec/PhpSpec/Exception/ErrorExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Example/NotEqualExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Example/StopOnFailureExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/ExceptionFactorySpec.php create mode 100644 spec/PhpSpec/Exception/ExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Fracture/ClassNotFoundExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Fracture/InterfaceNotImplementedExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Fracture/MethodNotFoundExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Fracture/MethodNotVisibleExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Fracture/NamedConstructorNotFoundExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Fracture/PropertyNotFoundExceptionSpec.php create mode 100644 spec/PhpSpec/Exception/Wrapper/InvalidCollaboratorTypeExceptionSpec.php create mode 100644 spec/PhpSpec/Formatter/BasicFormatterSpec.php create mode 100644 spec/PhpSpec/Formatter/DotFormatterSpec.php create mode 100644 spec/PhpSpec/Formatter/Html/HtmlIOSpec.php create mode 100644 spec/PhpSpec/Formatter/Html/ReportFailedItemSpec.php create mode 100644 spec/PhpSpec/Formatter/Html/ReportItemFactorySpec.php create mode 100644 spec/PhpSpec/Formatter/Html/ReportPassedItemSpec.php create mode 100644 spec/PhpSpec/Formatter/Html/ReportPendingItemSpec.php create mode 100644 spec/PhpSpec/Formatter/Html/TemplateSpec.php create mode 100644 spec/PhpSpec/Formatter/HtmlFormatterSpec.php create mode 100644 spec/PhpSpec/Formatter/JUnitFormatterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Differ/ArrayEngineSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Differ/DifferSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Differ/ObjectEngineSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Differ/StringEngineSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Exception/CallArgumentsPresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Exception/GenericPhpSpecExceptionPresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Exception/HtmlPhpSpecExceptionPresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionElementPresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionPresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Exception/TaggingExceptionElementPresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/SimplePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/TaggingPresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/ArrayTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/BaseExceptionTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/BooleanTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/CallableTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/ComposedValuePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/NullTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/ObjectTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/QuotingStringTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/Presenter/Value/TruncatingStringTypePresenterSpec.php create mode 100644 spec/PhpSpec/Formatter/ProgressFormatterSpec.php create mode 100644 spec/PhpSpec/Formatter/TapFormatterSpec.php create mode 100644 spec/PhpSpec/Listener/ClassNotFoundListenerSpec.php create mode 100644 spec/PhpSpec/Listener/CollaboratorMethodNotFoundListenerSpec.php create mode 100644 spec/PhpSpec/Listener/CollaboratorNotFoundListenerSpec.php create mode 100644 spec/PhpSpec/Listener/CurrentExampleListenerSpec.php create mode 100644 spec/PhpSpec/Listener/MethodNotFoundListenerSpec.php create mode 100644 spec/PhpSpec/Listener/MethodReturnedNullListenerSpec.php create mode 100644 spec/PhpSpec/Listener/NamedConstructorNotFoundListenerSpec.php create mode 100644 spec/PhpSpec/Listener/RerunListenerSpec.php create mode 100644 spec/PhpSpec/Listener/StatisticsCollectorSpec.php create mode 100644 spec/PhpSpec/Listener/StopOnFailureListenerSpec.php create mode 100644 spec/PhpSpec/Loader/Node/ExampleNodeSpec.php create mode 100644 spec/PhpSpec/Loader/Node/SpecificationNodeSpec.php create mode 100644 spec/PhpSpec/Loader/SuiteSpec.php create mode 100644 spec/PhpSpec/Loader/Transformer/InMemoryTypeHintIndexSpec.php create mode 100644 spec/PhpSpec/Loader/Transformer/TypeHintRewriterSpec.php create mode 100644 spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php create mode 100644 spec/PhpSpec/Locator/PSR0/PSR0ResourceSpec.php create mode 100644 spec/PhpSpec/Locator/PrioritizedResourceManagerSpec.php create mode 100644 spec/PhpSpec/Matcher/ApproximatelyMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ArrayContainMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ArrayCountMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ArrayKeyMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ArrayKeyValueMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/CallbackMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ComparisonMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/IdentityMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/Iterate/IterablesMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/Iterate/SubjectElementDoesNotMatchExceptionSpec.php create mode 100644 spec/PhpSpec/Matcher/Iterate/SubjectHasFewerElementsExceptionSpec.php create mode 100644 spec/PhpSpec/Matcher/Iterate/SubjectHasMoreElementsExceptionSpec.php create mode 100644 spec/PhpSpec/Matcher/IterateAsMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/IterateLikeMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ObjectStateMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ScalarMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/StartIteratingAsMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/StringContainMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/StringEndMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/StringRegexMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/StringStartMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/ThrowMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/TraversableContainMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/TraversableCountMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/TraversableKeyMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/TraversableKeyValueMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/TriggerMatcherSpec.php create mode 100644 spec/PhpSpec/Matcher/TypeMatcherSpec.php create mode 100644 spec/PhpSpec/Message/CurrentExampleTrackerSpec.php create mode 100644 spec/PhpSpec/NamespaceProvider/ComposerPsrNamespaceProviderSpec.php create mode 100644 spec/PhpSpec/Process/Context/JsonExecutionContextSpec.php create mode 100644 spec/PhpSpec/Process/Prerequisites/SuitePrerequisitesSpec.php create mode 100644 spec/PhpSpec/Process/ReRunner/CompositeReRunnerSpec.php create mode 100644 spec/PhpSpec/Process/ReRunner/OptionalReRunnerSpec.php create mode 100644 spec/PhpSpec/Process/ReRunner/PcntlReRunnerSpec.php create mode 100644 spec/PhpSpec/Process/ReRunner/ProcOpenReRunnerSpec.php create mode 100644 spec/PhpSpec/Process/ReRunner/WindowsPassthruReRunnerSpec.php create mode 100644 spec/PhpSpec/Process/Shutdown/ShutdownSpec.php create mode 100644 spec/PhpSpec/Process/Shutdown/UpdateConsoleActionSpec.php create mode 100644 spec/PhpSpec/Runner/CollaboratorManagerSpec.php create mode 100644 spec/PhpSpec/Runner/ExampleRunnerSpec.php create mode 100644 spec/PhpSpec/Runner/Maintainer/ErrorMaintainerSpec.php create mode 100644 spec/PhpSpec/Runner/Maintainer/MatchersMaintainerSpec.php create mode 100644 spec/PhpSpec/Runner/MatcherManagerSpec.php create mode 100644 spec/PhpSpec/Runner/SpecificationRunnerSpec.php create mode 100644 spec/PhpSpec/Runner/SuiteRunnerSpec.php create mode 100644 spec/PhpSpec/ServiceContainer/IndexedServiceContainerSpec.php create mode 100644 spec/PhpSpec/Specification/ErrorSpecificationSpec.php create mode 100644 spec/PhpSpec/Util/ClassFileAnalyserSpec.php create mode 100644 spec/PhpSpec/Util/ClassNameCheckerSpec.php create mode 100644 spec/PhpSpec/Util/ExampleObjectUsingTrait.php create mode 100644 spec/PhpSpec/Util/ExampleTrait.php create mode 100644 spec/PhpSpec/Util/InstantiatorSpec.php create mode 100644 spec/PhpSpec/Util/MethodAnalyserSpec.php create mode 100644 spec/PhpSpec/Util/ReservedWordsMethodNameCheckerSpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/CallerSpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/Expectation/ConstructorDecoratorSpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/Expectation/DecoratorSpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/Expectation/DispatcherDecoratorSpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/Expectation/NegativeSpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/Expectation/PositiveSpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/ExpectationFactorySpec.php create mode 100644 spec/PhpSpec/Wrapper/Subject/WrappedObjectSpec.php create mode 100644 spec/PhpSpec/Wrapper/SubjectSpec.php delete mode 100644 spec/Proget/Tests/PHPStan/PhpSpec/BarSpec.php delete mode 100644 spec/Proget/Tests/PHPStan/PhpSpec/FooSpec.php create mode 100644 src/Reflection/ObjectBehaviorMethodReflection.php create mode 100644 src/Reflection/ObjectBehaviorPropertiesClassReflectionExtension.php create mode 100644 src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php delete mode 100644 tests/Bar.php delete mode 100644 tests/Baz.php delete mode 100644 tests/Foo.php delete mode 100644 tests/ValueObject.php diff --git a/.php_cs b/.php_cs index e4c5613..5778a19 100644 --- a/.php_cs +++ b/.php_cs @@ -26,9 +26,8 @@ return PhpCsFixer\Config::create() ->setFinder( PhpCsFixer\Finder::create() ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') ->in(__DIR__ . '/spec') ) ->setRiskyAllowed(true) ->setUsingCache(false) -; \ No newline at end of file +; diff --git a/composer.json b/composer.json index cdb217c..3dfc34b 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,7 @@ }, "autoload-dev": { "psr-4": { - "Proget\\Tests\\PHPStan\\PhpSpec\\": "tests/", - "spec\\Proget\\": "spec/Proget/" + "spec\\PhpSpec\\": "spec/PhpSpec/" } }, "license": "MIT", @@ -37,7 +36,7 @@ "check-cs": "php-cs-fixer fix --dry-run --diff", "fix-cs": "php-cs-fixer fix", "tests": "phpspec run", - "stan": "phpstan analyse -l max -c ./phpstan.neon ./src ./tests ./spec", + "stan": "phpstan analyse -l max -c ./phpstan.neon ./src ./spec", "check": [ "@check-cs", "@stan", diff --git a/extension.neon b/extension.neon index a061f2e..5c5d80c 100644 --- a/extension.neon +++ b/extension.neon @@ -2,6 +2,12 @@ services: - class: Proget\PHPStan\PhpSpec\Reflection\ObjectBehaviorMethodsClassReflectionExtension tags: [phpstan.broker.methodsClassReflectionExtension] + - + class: Proget\PHPStan\PhpSpec\Reflection\ObjectBehaviorPropertiesClassReflectionExtension + tags: [phpstan.broker.propertiesClassReflectionExtension] + - + class: Proget\PHPStan\PhpSpec\Type\ObjectBehaviorDynamicMethodReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] extensions: phpspec: Proget\PHPStan\PhpSpec\DependencyInjection\CollaboratorExtension diff --git a/spec/PhpSpec/CodeAnalysis/MagicAwareAccessInspectorSpec.php b/spec/PhpSpec/CodeAnalysis/MagicAwareAccessInspectorSpec.php new file mode 100644 index 0000000..1367197 --- /dev/null +++ b/spec/PhpSpec/CodeAnalysis/MagicAwareAccessInspectorSpec.php @@ -0,0 +1,99 @@ +beConstructedWith($accessInspector); + } + + public function it_should_be_an_access_inspector() + { + $this->shouldImplement('PhpSpec\CodeAnalysis\AccessInspector'); + } + + public function it_should_detect_a_magic_getter_if_no_value_is_given() + { + $this->isPropertyReadable(new ObjectWithMagicGet, 'property')->shouldReturn(true); + } + + public function it_should_detect_a_magic_setter_if_a_value_is_given() + { + $this->isPropertyWritable(new ObjectWithMagicSet, 'property', true)->shouldReturn(true); + } + + public function it_should_detect_a_magic_call_method() + { + $this->isMethodCallable(new ObjectWithMagicCall, 'method')->shouldreturn(true); + } + + public function it_should_not_detect_a_getter_if_there_is_no_magic_getter_and_wrapped_inspector_finds_none(AccessInspector $accessInspector) + { + $accessInspector->isPropertyReadable(new \stdClass(), 'foo')->willReturn(false); + + $this->isPropertyReadable(new \stdClass(), 'foo')->shouldReturn(false); + } + + public function it_should_detect_a_getter_if_there_is_no_magic_getter_but_wrapped_inspector_finds_one(AccessInspector $accessInspector) + { + $accessInspector->isPropertyReadable(new \stdClass(), 'foo')->willReturn(true); + + $this->isPropertyReadable(new \stdClass(), 'foo')->shouldReturn(true); + } + + public function it_should_not_detect_a_setter_if_there_is_no_magic_setter_and_wrapped_inspector_finds_none(AccessInspector $accessInspector) + { + $accessInspector->isPropertyWritable(new \stdClass(), 'foo')->willReturn(false); + + $this->isPropertyWritable(new \stdClass(), 'foo')->shouldReturn(false); + } + + public function it_should_detect_a_setter_if_there_is_no_magic_setter_but_wrapped_inspector_finds_one(AccessInspector $accessInspector) + { + $accessInspector->isPropertyWritable(new \stdClass(), 'foo')->willReturn(true); + + $this->isPropertyWritable(new \stdClass(), 'foo')->shouldReturn(true); + } + + public function it_should_detect_a_method_if_there_is_no_magic_caller_and_wrapped_inspector_finds_none(AccessInspector $accessInspector) + { + $accessInspector->isMethodCallable(new \stdClass(), 'foo')->willReturn(false); + + $this->isMethodCallable(new \stdClass(), 'foo')->shouldReturn(false); + } + + public function it_should_detect_a_method_if_there_is_no_magic_caller_but_wrapped_inspector_finds_one(AccessInspector $accessInspector) + { + $accessInspector->isMethodCallable(new \stdClass(), 'foo')->willReturn(true); + + $this->isMethodCallable(new \stdClass(), 'foo')->shouldReturn(true); + } +} + +class ObjectWithMagicGet +{ + public function __get($name) + { + } +} + +class ObjectWithMagicSet +{ + public function __set($name, $value) + { + } +} + +class ObjectWithMagicCall +{ + public function __call($name, $args) + { + } +} diff --git a/spec/PhpSpec/CodeAnalysis/StaticRejectingNamespaceResolverSpec.php b/spec/PhpSpec/CodeAnalysis/StaticRejectingNamespaceResolverSpec.php new file mode 100644 index 0000000..c7995e8 --- /dev/null +++ b/spec/PhpSpec/CodeAnalysis/StaticRejectingNamespaceResolverSpec.php @@ -0,0 +1,45 @@ +beConstructedWith($namespaceResolver); + } + + public function it_is_initializable() + { + $this->shouldHaveType('PhpSpec\CodeAnalysis\NamespaceResolver'); + } + + public function it_delegates_analysis_to_wrapped_resolver(NamespaceResolver $namespaceResolver) + { + $this->analyse('foo'); + + $namespaceResolver->analyse('foo')->shouldhaveBeenCalled(); + } + + public function it_delegates_resolution_to_wrapped_resolver(NamespaceResolver $namespaceResolver) + { + $namespaceResolver->resolve('Bar')->willReturn('Foo\Bar'); + + $this->resolve('Bar')->shouldReturn('Foo\Bar'); + } + + public function it_does_not_allow_resolution_of_non_object_types() + { + $this->shouldThrow(DisallowedNonObjectTypehintException::class)->duringResolve('int'); + $this->shouldThrow(DisallowedNonObjectTypehintException::class)->duringResolve('float'); + $this->shouldThrow(DisallowedNonObjectTypehintException::class)->duringResolve('string'); + $this->shouldThrow(DisallowedNonObjectTypehintException::class)->duringResolve('bool'); + $this->shouldThrow(DisallowedNonObjectTypehintException::class)->duringResolve('iterable'); + } +} diff --git a/spec/PhpSpec/CodeAnalysis/TokenizedNamespaceResolverSpec.php b/spec/PhpSpec/CodeAnalysis/TokenizedNamespaceResolverSpec.php new file mode 100644 index 0000000..bfb31b9 --- /dev/null +++ b/spec/PhpSpec/CodeAnalysis/TokenizedNamespaceResolverSpec.php @@ -0,0 +1,128 @@ +shouldHaveType('PhpSpec\CodeAnalysis\NamespaceResolver'); + } + + public function it_resolves_types_outside_of_namespaces() + { + $this->analyse(' + resolve('Bar')->shouldReturn('Bar'); + $this->resolve('Bar')->shouldReturn('Bar'); + } + + public function it_resolves_types_from_current_namespace() + { + $this->analyse(' + resolve('Foo')->shouldReturn('Baz\Foo'); + $this->resolve('Bar')->shouldReturn('Baz\Bar'); + } + + public function it_resolves_types_with_use_statements() + { + $this->analyse(' + resolve('Foo')->shouldReturn('Baz\Foo'); + $this->resolve('Bar')->shouldReturn('Boz\Bar'); + } + + public function it_resolves_types_with_use_aliases() + { + $this->analyse(' + resolve('Foo')->shouldReturn('Baz\Foo'); + $this->resolve('Biz')->shouldReturn('Boz\Bar'); + } + + public function it_resolves_types_with_partial_use_statements() + { + $this->analyse(' + resolve('Foo')->shouldReturn('Baz\Foo'); + $this->resolve('Bar\Baz')->shouldReturn('Boz\Bar\Baz'); + } + + public function it_resolves_types_from_grouped_use_statements() + { + $this->analyse(' + resolve('Fiz')->shouldReturn('Boz\Fiz'); + $this->resolve('Buz')->shouldReturn('Boz\Buz'); + } +} diff --git a/spec/PhpSpec/CodeAnalysis/TokenizedTypeHintRewriterSpec.php b/spec/PhpSpec/CodeAnalysis/TokenizedTypeHintRewriterSpec.php new file mode 100644 index 0000000..5cd4fca --- /dev/null +++ b/spec/PhpSpec/CodeAnalysis/TokenizedTypeHintRewriterSpec.php @@ -0,0 +1,260 @@ +beConstructedWith($typeHintIndex, $namespaceResolver); + $namespaceResolver->resolve(Argument::cetera())->willReturn('someClass'); + $namespaceResolver->analyse(Argument::any())->shouldBeCalled(); + } + + public function it_is_a_typehint_rewriter(TypeHintIndex $typeHintIndex, NamespaceResolver $namespaceResolver) + { + $this->beConstructedWith($typeHintIndex, $namespaceResolver); + $namespaceResolver->resolve(Argument::cetera())->willReturn('someClass'); + $namespaceResolver->analyse(Argument::any())->shouldNotBeCalled(); + + $this->shouldHaveType('PhpSpec\CodeAnalysis\TypeHintRewriter'); + } + + public function it_leaves_alone_specs_with_no_typehints() + { + $this->rewrite(' + shouldReturn(' + rewrite(' + shouldReturn(' + rewrite(' + shouldReturn(' + rewrite(' + shouldReturn(' + analyse(Argument::any())->shouldBeCalled(); + + $namespaceResolver->resolve('FooSpec')->willReturn('FooSpec'); + $namespaceResolver->resolve('Foo\Bar')->willReturn('Foo\Bar'); + $namespaceResolver->resolve('Baz')->willReturn('Baz'); + + $this->rewrite(' + add('FooSpec', 'bar', '$bar', 'Foo\Bar')->shouldHaveBeenCalled(); + $typeHintIndex->add('FooSpec', 'bar', '$baz', 'Baz')->shouldHaveBeenCalled(); + } + + public function it_indexes_invalid_typehints( + TypeHintIndex $typeHintIndex, + NamespaceResolver $namespaceResolver + ) { + $e = new DisallowedNonObjectTypehintException(); + $namespaceResolver->analyse(Argument::any())->shouldBeCalled(); + + $namespaceResolver->resolve('FooSpec')->willReturn('FooSpec'); + $namespaceResolver->resolve('int')->willThrow($e); + + $this->rewrite(' + addInvalid('FooSpec', 'bar', '$bar', $e)->shouldHaveBeenCalled(); + $typeHintIndex->add('FooSpec', 'bar', '$bar', Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_preserves_line_numbers() + { + $this->rewrite(' + shouldReturn(' + rewrite(' + shouldReturn(' + shouldImplement('PhpSpec\CodeAnalysis\AccessInspector'); + } + + public function it_should_reject_an_object_if_the_property_does_not_exist() + { + $this->isPropertyReadable(new ObjectWithNoProperty, 'property')->shouldReturn(false); + $this->isPropertyWritable(new ObjectWithNoProperty, 'property')->shouldReturn(false); + } + + public function it_should_reject_a_private_property() + { + $this->isPropertyReadable(new ObjectWithPrivateProperty, 'property')->shouldReturn(false); + $this->isPropertyWritable(new ObjectWithPrivateProperty, 'property')->shouldReturn(false); + } + + public function it_should_detect_a_public_property() + { + $this->isPropertyReadable(new ObjectWithPublicProperty, 'property')->shouldReturn(true); + $this->isPropertyWritable(new ObjectWithPublicProperty, 'property')->shouldReturn(true); + } + + public function it_should_reject_an_object_if_a_method_does_not_exist() + { + $this->isMethodCallable(new ObjectWithNoMethod, 'method')->shouldReturn(false); + } + + public function it_should_reject_a_private_method() + { + $this->isMethodCallable(new ObjectWithPrivateMethod, 'method')->shouldReturn(false); + } + + public function it_should_detect_a_public_method() + { + $this->isMethodCallable(new ObjectWithPublicMethod, 'method')->shouldReturn(true); + } +} + +class ObjectWithNoProperty +{ +} + +class ObjectWithPrivateProperty +{ + private $property; +} + +class ObjectWithPublicProperty +{ + public $property; +} + +class ObjectWithNoMethod +{ +} + +class ObjectWithPrivateMethod +{ + private function method() + { + } +} + +class ObjectWithPublicMethod +{ + public function method() + { + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/ClassGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/ClassGeneratorSpec.php new file mode 100644 index 0000000..9afb881 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/ClassGeneratorSpec.php @@ -0,0 +1,154 @@ +beConstructedWith($io, $tpl, $fs, $executionContext); + } + + public function it_is_a_generator() + { + $this->shouldBeAnInstanceOf('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_supports_class_generation(Resource $resource) + { + $this->supports($resource, 'class', [])->shouldReturn(true); + } + + public function it_does_not_support_anything_else(Resource $resource) + { + $this->supports($resource, 'anything_else', [])->shouldReturn(false); + } + + public function its_priority_is_0() + { + $this->getPriority()->shouldReturn(0); + } + + public function it_generates_class_from_resource_and_puts_it_into_appropriate_folder( + $io, + TemplateRenderer $tpl, + $fs, + Resource $resource + ) { + $resource->getName()->willReturn('App'); + $resource->getSrcFilename()->willReturn('/project/src/Acme/App.php'); + $resource->getSrcNamespace()->willReturn('Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + + $values = [ + '%filepath%' => '/project/src/Acme/App.php', + '%name%' => 'App', + '%namespace%' => 'Acme', + '%namespace_block%' => "\n\nnamespace Acme;", + ]; + + $tpl->render('class', $values)->willReturn(''); + $tpl->renderString(Argument::type('string'), $values)->willReturn('generated code'); + + $fs->pathExists('/project/src/Acme/App.php')->willReturn(false); + $fs->isDirectory('/project/src/Acme')->willReturn(true); + $fs->putFileContents('/project/src/Acme/App.php', 'generated code')->shouldBeCalled(); + + $this->generate($resource); + } + + public function it_uses_template_provided_by_templating_system_if_there_is_one( + $io, + $tpl, + $fs, + Resource $resource + ) { + $resource->getName()->willReturn('App'); + $resource->getSrcFilename()->willReturn('/project/src/Acme/App.php'); + $resource->getSrcNamespace()->willReturn('Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + + $values = [ + '%filepath%' => '/project/src/Acme/App.php', + '%name%' => 'App', + '%namespace%' => 'Acme', + '%namespace_block%' => "\n\nnamespace Acme;", + ]; + + $tpl->render('class', $values)->willReturn('template code'); + $tpl->renderString(Argument::type('string'), $values)->willReturn('generated code'); + + $fs->pathExists('/project/src/Acme/App.php')->willReturn(false); + $fs->isDirectory('/project/src/Acme')->willReturn(true); + $fs->putFileContents('/project/src/Acme/App.php', 'template code')->shouldBeCalled(); + + $this->generate($resource); + } + + public function it_creates_folder_for_class_if_needed($io, TemplateRenderer $tpl, $fs, Resource $resource) + { + $tpl->render('class', Argument::type('array'))->willReturn('rendered string'); + $resource->getName()->willReturn('App'); + $resource->getSrcFilename()->willReturn('/project/src/Acme/App.php'); + $resource->getSrcNamespace()->willReturn('Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + + $fs->pathExists('/project/src/Acme/App.php')->willReturn(false); + $fs->isDirectory('/project/src/Acme')->willReturn(false); + $fs->makeDirectory('/project/src/Acme')->shouldBeCalled(); + $fs->putFileContents('/project/src/Acme/App.php', Argument::any())->willReturn(null); + + $this->generate($resource); + } + + public function it_asks_confirmation_if_class_already_exists( + $io, + $tpl, + $fs, + Resource $resource + ) { + $resource->getName()->willReturn('App'); + $resource->getSrcFilename()->willReturn('/project/src/Acme/App.php'); + $resource->getSrcNamespace()->willReturn('Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + + $fs->pathExists('/project/src/Acme/App.php')->willReturn(true); + $io->askConfirmation(Argument::type('string'), false)->willReturn(false); + + $fs->putFileContents(Argument::cetera())->shouldNotBeCalled(); + + $this->generate($resource); + } + + public function it_records_that_class_was_created_in_executioncontext( + Resource $resource, + ExecutionContext $executionContext, + TemplateRenderer $tpl, + Filesystem $fs + ) { + $tpl->render('class', Argument::type('array'))->willReturn('rendered string'); + $fs->isDirectory('/project/src/Acme')->willReturn(true); + $fs->pathExists('/project/src/Acme/App.php')->willReturn(false); + $fs->putFileContents('/project/src/Acme/App.php', Argument::any())->shouldBeCalled(); + + $resource->getName()->willReturn('App'); + $resource->getSrcFilename()->willReturn('/project/src/Acme/App.php'); + $resource->getSrcNamespace()->willReturn('Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + + $this->generate($resource); + + $executionContext->addGeneratedType('Acme\App')->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/ConfirmingGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/ConfirmingGeneratorSpec.php new file mode 100644 index 0000000..70a4988 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/ConfirmingGeneratorSpec.php @@ -0,0 +1,59 @@ +beConstructedWith($io, 'Question for {CLASSNAME}', $generator); + } + + public function it_is_a_Generator() + { + $this->shouldHaveType('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_supports_the_same_generator_as_its_parent(Generator $generator, Resource $resource) + { + $generator->supports($resource, 'generation', [])->willReturn(true); + + $this->supports($resource, 'generation', [])->shouldReturn(true); + } + + public function it_has_the_same_priority_as_its_parent(Generator $generator) + { + $generator->getPriority()->willReturn(1324); + + $this->getPriority()->shouldReturn(1324); + } + + public function it_does_not_call_the_parent_generate_method_if_the_user_answers_no(Generator $generator, Resource $resource, ConsoleIO $io) + { + $resource->getSrcClassname()->willReturn('Namespace/Classname'); + + $io->askConfirmation('Question for Namespace/Classname')->willReturn(false); + + $this->generate($resource, []); + + $generator->generate($resource, [])->shouldNotHaveBeenCalled(); + } + + public function it_calls_the_parent_generate_method_if_the_user_answers_yes(Generator $generator, Resource $resource, ConsoleIO $io) + { + $resource->getSrcClassname()->willReturn('Namespace/Classname'); + + $io->askConfirmation('Question for Namespace/Classname')->willReturn(true); + + $this->generate($resource, []); + + $generator->generate($resource, [])->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/MethodGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/MethodGeneratorSpec.php new file mode 100644 index 0000000..e497c2e --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/MethodGeneratorSpec.php @@ -0,0 +1,84 @@ +beConstructedWith($io, $tpl, $fs, $codeWriter); + } + + public function it_is_a_generator() + { + $this->shouldBeAnInstanceOf('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_supports_method_generation(Resource $resource) + { + $this->supports($resource, 'method', [])->shouldReturn(true); + } + + public function it_does_not_support_anything_else(Resource $resource) + { + $this->supports($resource, 'anything_else', [])->shouldReturn(false); + } + + public function its_priority_is_0() + { + $this->getPriority()->shouldReturn(0); + } + + public function it_generates_class_method_from_resource($io, $tpl, $fs, Resource $resource, CodeWriter $codeWriter) + { + $codeWithoutMethod = << 'setName', + '%arguments%' => '$argument1', + ]; + + $resource->getSrcFilename()->willReturn('/project/src/Acme/App.php'); + $resource->getSrcClassname()->willReturn('Acme\App'); + + $tpl->render('method', $values)->willReturn(''); + $tpl->renderString(Argument::type('string'), $values)->willReturn('METHOD'); + + $codeWriter->insertMethodLastInClass($codeWithoutMethod, 'METHOD')->willReturn($codeWithMethod); + + $fs->getFileContents('/project/src/Acme/App.php')->willReturn($codeWithoutMethod); + $fs->putFileContents('/project/src/Acme/App.php', $codeWithMethod)->shouldBeCalled(); + + $this->generate($resource, ['name' => 'setName', 'arguments' => ['everzet']]); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/NamedConstructorGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/NamedConstructorGeneratorSpec.php new file mode 100644 index 0000000..2688669 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/NamedConstructorGeneratorSpec.php @@ -0,0 +1,87 @@ +beConstructedWith($io, $tpl, $fs, $codeWriter); + } + + public function it_is_a_generator() + { + $this->shouldBeAnInstanceOf('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_supports_static_constructor_generation(Resource $resource) + { + $this->supports($resource, 'named_constructor', [])->shouldReturn(true); + } + + public function it_does_not_support_anything_else(Resource $resource) + { + $this->supports($resource, 'anything_else', [])->shouldReturn(false); + } + + public function its_priority_is_0() + { + $this->getPriority()->shouldReturn(0); + } + + public function it_generates_static_constructor_method_from_resource($io, $tpl, $fs, Resource $resource, CodeWriter $codeWriter) + { + $codeWithoutMethod = << 'setName', + '%arguments%' => '$argument1', + '%returnVar%' => '$app', + '%className%' => 'App', + '%constructorArguments%' => '' + ]; + + $resource->getSrcFilename()->willReturn('/project/src/Acme/App.php'); + $resource->getSrcClassname()->willReturn('Acme\App'); + $resource->getName()->willReturn('App'); + + $tpl->render('named_constructor_create_object', $values)->willReturn(''); + $tpl->renderString(Argument::type('string'), $values)->willReturn('METHOD'); + + $codeWriter->insertAfterMethod($codeWithoutMethod, '__construct', 'METHOD')->willReturn($codeWithMethod); + + $fs->getFileContents('/project/src/Acme/App.php')->willReturn($codeWithoutMethod); + $fs->putFileContents('/project/src/Acme/App.php', $codeWithMethod)->shouldBeCalled(); + + $this->generate($resource, ['name' => 'setName', 'arguments' => ['jmurphy']]); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/NewFileNotifyingGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/NewFileNotifyingGeneratorSpec.php new file mode 100644 index 0000000..7e6e283 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/NewFileNotifyingGeneratorSpec.php @@ -0,0 +1,121 @@ +beConstructedWith($generator, $dispatcher, $filesystem); + } + + public function it_is_a_code_generator() + { + $this->shouldImplement('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_should_proxy_the_support_call_to_the_decorated_object($generator, Resource $resource) + { + $generator->supports($resource, 'foo', ['bar'])->willReturn(true); + $this->supports($resource, 'foo', ['bar'])->shouldReturn(true); + } + + public function it_should_proxy_the_priority_call_to_the_decorated_object($generator) + { + $generator->getPriority()->willReturn(5); + $this->getPriority()->shouldReturn(5); + } + + public function it_should_proxy_the_generate_call_to_the_decorated_object(Generator $generator, Resource $resource, Filesystem $filesystem) + { + $generator->supports(Argument::cetera())->willReturn(true); + $resource->getSpecFilename()->willReturn(''); + $filesystem->pathExists(Argument::any())->willReturn(true); + + $generator->generate($resource, [])->shouldBeCalled(); + + $this->generate($resource, []); + } + + public function it_should_dispatch_an_event_when_a_file_is_created(Generator $generator, $dispatcher, $filesystem, Resource $resource) + { + $generator->supports(Argument::cetera())->willReturn(false); + $path = '/foo'; + $resource->getSrcFilename()->willReturn($path); + $event = new FileCreationEvent($path); + $filesystem->pathExists($path)->willReturn(false, true); + $generator->generate($resource, [])->shouldBeCalled(); + + $this->generate($resource, []); + + $dispatcher->dispatch('afterFileCreation', $event)->shouldHaveBeenCalled(); + } + + public function it_should_dispatch_an_event_with_the_spec_path_when_a_spec_is_created($generator, $dispatcher, $filesystem, Resource $resource) + { + $path = '/foo'; + $generator->supports($resource, 'specification', [])->willReturn(true); + $generator->generate(Argument::cetera())->shouldBeCalled(); + $resource->getSpecFilename()->willReturn($path); + $filesystem->pathExists($path)->willReturn(false, true); + $event = new FileCreationEvent($path); + + $this->generate($resource, []); + + $dispatcher->dispatch('afterFileCreation', $event)->shouldHaveBeenCalled(); + } + + public function it_should_check_that_the_file_was_created($generator, $filesystem, Resource $resource) + { + $path = '/foo'; + $resource->getSrcFilename()->willReturn($path); + + $filesystem->pathExists($path)->willReturn(false); + + $generator->supports(Argument::cetera())->willReturn(false); + $generator->generate($resource, [])->will(function () use ($filesystem, $path) { + $filesystem->pathExists($path)->willReturn(true); + }); + + $this->generate($resource, []); + } + + public function it_should_not_dispatch_an_event_if_the_file_was_not_created(Generator $generator, $dispatcher, $filesystem, Resource $resource) + { + $generator->supports(Argument::cetera())->willReturn(false); + $generator->generate($resource, [])->shouldBeCalled(); + $path = '/foo'; + $resource->getSrcFilename()->willReturn($path); + $filesystem->pathExists($path)->willReturn(false); + + $this->generate($resource, []); + + $dispatcher->dispatch('afterFileCreation', Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_should_not_dispatch_an_event_if_the_file_already_existed(Generator $generator, $dispatcher, $filesystem, Resource $resource) + { + $generator->supports(Argument::cetera())->willReturn(false); + $generator->generate($resource, [])->shouldBeCalled(); + $path = '/foo'; + $resource->getSrcFilename()->willReturn($path); + + $filesystem->pathExists($path)->willReturn(true); + + $this->generate($resource, []); + + $dispatcher->dispatch('afterFileCreation', Argument::any())->shouldNotHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/OneTimeGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/OneTimeGeneratorSpec.php new file mode 100644 index 0000000..111f072 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/OneTimeGeneratorSpec.php @@ -0,0 +1,56 @@ +beConstructedWith($generator); + } + + public function it_is_a_Generator() + { + $this->shouldHaveType('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_supports_the_same_generator_as_its_parent(Generator $generator, Resource $resource) + { + $generator->supports($resource, 'generation', [])->willReturn(true); + + $this->supports($resource, 'generation', [])->shouldReturn(true); + } + + public function it_has_the_same_priority_as_its_parent(Generator $generator) + { + $generator->getPriority()->willReturn(1324); + + $this->getPriority()->shouldReturn(1324); + } + + public function it_calls_the_parent_generate_method_just_once_for_the_same_classname(Generator $generator, Resource $resource) + { + $resource->getSrcClassname()->willReturn('Namespace/Classname'); + + $this->generate($resource, []); + $this->generate($resource, []); + + $generator->generate($resource, [])->shouldHaveBeenCalledTimes(1); + } + + public function it_calls_the_parent_generate_method_once_per_each_classname(Generator $generator, Resource $resource) + { + $resource->getSrcClassname()->willReturn('Namespace/Classname1', 'Namespace/Classname2'); + + $this->generate($resource, []); + $this->generate($resource, []); + + $generator->generate($resource, [])->shouldHaveBeenCalledTimes(2); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/ReturnConstantGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/ReturnConstantGeneratorSpec.php new file mode 100644 index 0000000..a1e1d66 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/ReturnConstantGeneratorSpec.php @@ -0,0 +1,39 @@ +beConstructedWith($io, $templates, $filesystem); + } + + public function it_is_a_generator() + { + $this->shouldHaveType('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_supports_returnConstant_generation(Resource $resource) + { + $this->supports($resource, 'returnConstant', [])->shouldReturn(true); + } + + public function it_does_not_support_anything_else(Resource $resource) + { + $this->supports($resource, 'anything_else', [])->shouldReturn(false); + } + + public function its_priority_is_0() + { + $this->getPriority()->shouldReturn(0); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/SpecificationGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/SpecificationGeneratorSpec.php new file mode 100644 index 0000000..a681c1d --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/SpecificationGeneratorSpec.php @@ -0,0 +1,138 @@ +beConstructedWith($io, $tpl, $fs, $context); + } + + public function it_is_a_generator() + { + $this->shouldBeAnInstanceOf('PhpSpec\CodeGenerator\Generator\Generator'); + } + + public function it_supports_specification_generations(Resource $resource) + { + $this->supports($resource, 'specification', [])->shouldReturn(true); + } + + public function it_does_not_support_anything_else(Resource $resource) + { + $this->supports($resource, 'anything_else', [])->shouldReturn(false); + } + + public function its_priority_is_0() + { + $this->getPriority()->shouldReturn(0); + } + + public function it_generates_spec_class_from_resource_and_puts_it_into_appropriate_folder( + $io, + $tpl, + $fs, + Resource $resource + ) { + $resource->getSpecName()->willReturn('AppSpec'); + $resource->getSpecFilename()->willReturn('/project/spec/Acme/AppSpec.php'); + $resource->getSpecNamespace()->willReturn('spec\Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + $resource->getName()->willReturn('App'); + + $values = [ + '%filepath%' => '/project/spec/Acme/AppSpec.php', + '%name%' => 'AppSpec', + '%namespace%' => 'spec\Acme', + '%subject%' => 'Acme\App', + '%subject_class%' => 'App' + ]; + + $tpl->render('specification', $values)->willReturn(''); + $tpl->renderString(Argument::type('string'), $values)->willReturn('generated code'); + + $fs->pathExists('/project/spec/Acme/AppSpec.php')->willReturn(false); + $fs->isDirectory('/project/spec/Acme')->willReturn(true); + $fs->putFileContents('/project/spec/Acme/AppSpec.php', 'generated code')->shouldBeCalled(); + + $this->generate($resource); + } + + public function it_uses_template_provided_by_templating_system_if_there_is_one( + $io, + $tpl, + $fs, + Resource $resource + ) { + $resource->getSpecName()->willReturn('AppSpec'); + $resource->getSpecFilename()->willReturn('/project/spec/Acme/AppSpec.php'); + $resource->getSpecNamespace()->willReturn('spec\Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + $resource->getName()->willReturn('App'); + + $values = [ + '%filepath%' => '/project/spec/Acme/AppSpec.php', + '%name%' => 'AppSpec', + '%namespace%' => 'spec\Acme', + '%subject%' => 'Acme\App', + '%subject_class%' => 'App' + ]; + + $tpl->render('specification', $values)->willReturn('template code'); + $tpl->renderString(Argument::type('string'), $values)->willReturn('generated code'); + + $fs->pathExists('/project/spec/Acme/AppSpec.php')->willReturn(false); + $fs->isDirectory('/project/spec/Acme')->willReturn(true); + $fs->putFileContents('/project/spec/Acme/AppSpec.php', 'template code')->shouldBeCalled(); + + $this->generate($resource); + } + + public function it_creates_folder_for_spec_if_needed($io, TemplateRenderer $tpl, $fs, Resource $resource) + { + $tpl->render('specification', Argument::type('array'))->willReturn('rendered string'); + $resource->getSpecName()->willReturn('AppAppSpec'); + $resource->getSpecFilename()->willReturn('/project/spec/Acme/AppSpec.php'); + $resource->getSpecNamespace()->willReturn('spec\Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + $resource->getName()->willReturn('App'); + + $fs->pathExists('/project/spec/Acme/AppSpec.php')->willReturn(false); + $fs->isDirectory('/project/spec/Acme')->willReturn(false); + $fs->makeDirectory('/project/spec/Acme')->shouldBeCalled(); + $fs->putFileContents('/project/spec/Acme/AppSpec.php', Argument::any())->willReturn(null); + + $this->generate($resource); + } + + public function it_asks_confirmation_if_spec_already_exists( + $io, + $tpl, + $fs, + Resource $resource + ) { + $resource->getSpecName()->willReturn('AppSpec'); + $resource->getSpecFilename()->willReturn('/project/spec/Acme/AppSpec.php'); + $resource->getSpecNamespace()->willReturn('spec\Acme'); + $resource->getSrcClassname()->willReturn('Acme\App'); + + $fs->pathExists('/project/spec/Acme/AppSpec.php')->willReturn(true); + $io->askConfirmation(Argument::type('string'), false)->willReturn(false); + + $fs->putFileContents(Argument::cetera())->shouldNotBeCalled(); + + $this->generate($resource); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Generator/ValidateClassNameSpecificationGeneratorSpec.php b/spec/PhpSpec/CodeGenerator/Generator/ValidateClassNameSpecificationGeneratorSpec.php new file mode 100644 index 0000000..10cf8c0 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Generator/ValidateClassNameSpecificationGeneratorSpec.php @@ -0,0 +1,74 @@ +beConstructedWith($classNameChecker, $io, $originalGenerator); + } + + public function it_is_initializable() + { + $this->shouldHaveType(ValidateClassNameSpecificationGenerator::class); + } + + public function it_supports_generation_when_original_generator_supports_it( + Generator $originalGenerator, + Resource $resource + ) { + $originalGenerator->supports($resource, '', [])->willReturn(true); + + $this->supports($resource, '', [])->shouldReturn(true); + } + + public function it_does_not_support_generation_when_original_generator_doesnt( + Generator $originalGenerator, + Resource $resource + ) { + $originalGenerator->supports($resource, '', [])->willReturn(false); + + $this->supports($resource, '', [])->shouldReturn(false); + } + + public function it_delegates_generation_to_original_generator_for_valid_class_name( + Generator $originalGenerator, + Resource $resource, + NameChecker $classNameChecker + ) { + $className = 'Acme\Markdown'; + $resource->getSrcClassname()->willReturn($className); + $classNameChecker->isNameValid($className)->willReturn(true); + + $originalGenerator->generate($resource, [])->shouldBeCalled(); + + $this->generate($resource, []); + } + + public function it_prints_error_and_skips_generation_for_invalid_class_name( + Generator $originalGenerator, + Resource $resource, + NameChecker $classNameChecker, + ConsoleIO $io + ) { + $className = 'Acme\Markdown'; + $resource->getSrcClassname()->willReturn($className); + $classNameChecker->isNameValid($className)->willReturn(false); + + $io->writeBrokenCodeBlock(Argument::containingString('because class name contains reserved keyword'), 2)->shouldBeCalled(); + $originalGenerator->generate($resource, [])->shouldNotBeCalled(); + + $this->generate($resource, []); + } +} diff --git a/spec/PhpSpec/CodeGenerator/GeneratorManagerSpec.php b/spec/PhpSpec/CodeGenerator/GeneratorManagerSpec.php new file mode 100644 index 0000000..22b29b4 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/GeneratorManagerSpec.php @@ -0,0 +1,50 @@ +getPriority()->willReturn(0); + $generator->supports($resource, 'specification', [])->willReturn(true); + $generator->generate($resource, [])->shouldBeCalled(); + + $this->registerGenerator($generator); + $this->generate($resource, 'specification'); + } + + public function it_chooses_generator_by_priority( + Generator $generator1, + Generator $generator2, + Resource $resource + ) { + $generator1->supports($resource, 'class', ['class' => 'CustomLoader']) + ->willReturn(true); + $generator1->getPriority()->willReturn(0); + $generator2->supports($resource, 'class', ['class' => 'CustomLoader']) + ->willReturn(true); + $generator2->getPriority()->willReturn(2); + + $generator1->generate($resource, ['class' => 'CustomLoader'])->shouldNotBeCalled(); + $generator2->generate($resource, ['class' => 'CustomLoader'])->shouldBeCalled(); + + $this->registerGenerator($generator1); + $this->registerGenerator($generator2); + $this->generate($resource, 'class', ['class' => 'CustomLoader']); + } + + public function it_throws_exception_if_no_generator_found(Resource $resource) + { + $this->shouldThrow()->duringGenerate($resource, 'class', ['class' => 'CustomLoader']); + } +} diff --git a/spec/PhpSpec/CodeGenerator/TemplateRendererSpec.php b/spec/PhpSpec/CodeGenerator/TemplateRendererSpec.php new file mode 100644 index 0000000..963eaa7 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/TemplateRendererSpec.php @@ -0,0 +1,99 @@ +beConstructedWith($fs); + } + + public function it_does_not_have_registered_locations_by_default() + { + $this->getLocations()->shouldHaveCount(0); + } + + public function it_has_locations_setter() + { + $this->setLocations(['location1', 'location2']); + $this->getLocations()->shouldReturn(['location1', 'location2']); + } + + public function it_provides_a_method_to_prepend_location() + { + $this->setLocations(['location1', 'location2']); + $this->prependLocation('location0'); + + $this->getLocations()->shouldReturn(['location0', 'location1', 'location2']); + } + + public function it_provides_a_method_to_append_location() + { + $this->setLocations(['location1', 'location2']); + $this->appendLocation('location0'); + + $this->getLocations()->shouldReturn(['location1', 'location2', 'location0']); + } + + public function it_normalizes_locations() + { + $this->setLocations(['lo/ca\\tion', '\\location', 'location\\']); + $this->getLocations()->shouldReturn([ + 'lo'.DIRECTORY_SEPARATOR.'ca'.DIRECTORY_SEPARATOR.'tion', + DIRECTORY_SEPARATOR.'location', + 'location' + ]); + } + + public function it_reads_existing_file_from_registered_location($fs) + { + $fs->pathExists('location1'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn(true); + $fs->getFileContents('location1'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn('cont'); + + $this->setLocations(['location1']); + $this->render('some_file')->shouldReturn('cont'); + } + + public function it_reads_existing_file_from_first_registered_location($fs) + { + $fs->pathExists('location1'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn(false); + $fs->pathExists('location2'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn(true); + $fs->pathExists('location3'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn(true); + $fs->getFileContents('location2'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn('cont'); + $fs->getFileContents('location3'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn('cont2'); + + $this->setLocations(['location1', 'location2', 'location3']); + $this->render('some_file')->shouldReturn('cont'); + } + + public function it_replaces_placeholders_in_template_with_provided_values($fs) + { + $fs->pathExists('location1'.DIRECTORY_SEPARATOR.'some_file.tpl')->willReturn(true); + $fs->getFileContents('location1'.DIRECTORY_SEPARATOR.'some_file.tpl') + ->willReturn('Template #%number%. From %spec_name% spec.'); + + $this->setLocations(['location1']); + $this->render('some_file', ['%number%' => 2, '%spec_name%' => 'tpl']) + ->shouldReturn('Template #2. From tpl spec.'); + } + + public function it_can_render_template_from_string() + { + $this->renderString('Template #%number%. From %spec_name% spec.', [ + '%number%' => 2, + '%spec_name%' => 'tpl' + ])->shouldReturn('Template #2. From tpl spec.'); + } + + public function it_returns_empty_string_if_template_is_not_found_in_any_registered_locations() + { + $this->render('some_file')->shouldReturn(''); + } +} diff --git a/spec/PhpSpec/CodeGenerator/Writer/TokenizedCodeWriterSpec.php b/spec/PhpSpec/CodeGenerator/Writer/TokenizedCodeWriterSpec.php new file mode 100644 index 0000000..bb67916 --- /dev/null +++ b/spec/PhpSpec/CodeGenerator/Writer/TokenizedCodeWriterSpec.php @@ -0,0 +1,290 @@ +beConstructedWith(new ClassFileAnalyser()); + } + + public function it_should_write_the_content_after_the_last_method() + { + $class = $this->getSingleMethodClass(); + $method = $this->getMethod(); + $result = $this->getClassWithNewMethodLast(); + + $this->insertMethodLastInClass($class, $method)->shouldReturn($result); + } + + public function it_should_write_the_content_before_the_first_method() + { + $class = $this->getSingleMethodClass(); + $method = $this->getMethod(); + $result = $this->getClassWithNewMethodFirst(); + + $this->insertMethodFirstInClass($class, $method)->shouldReturn($result); + } + + public function it_should_write_a_method_after_another_method() + { + $class = $this->getClassWithTwoMethods(); + $method = $this->getMethod(); + $result = $this->getClassWithNewMethodInMiddle(); + + $this->insertAfterMethod($class, 'methodOne', $method)->shouldReturn($result); + } + + public function it_should_handle_no_methods_when_writing_method_at_end() + { + $class = $this->getClassWithNoMethods(); + $method = $this->getMethod(); + $result = $this->getClassWithOnlyNewMethod(); + + $this->insertMethodLastInClass($class, $method)->shouldReturn($result); + } + + public function it_should_handle_no_methods_when_writing_method_at_start() + { + $class = $this->getClassWithNoMethods(); + $method = $this->getMethod(); + $result = $this->getClassWithOnlyNewMethod(); + + $this->insertMethodFirstInClass($class, $method)->shouldReturn($result); + } + + public function it_should_throw_an_exception_if_a_specific_method_is_not_found() + { + $class = $this->getClassWithNoMethods(); + + $exception = new NamedMethodNotFoundException('Target method not found'); + + $this->shouldThrow($exception)->during('insertAfterMethod', [$class, 'methodOne', '']); + } + + public function it_should_generate_a_method_in_a_class_with_a_string_containing_braces() + { + $class = $this->getClassWithBraceText(); + $method = $this->getMethod(); + $result = $this->getClassWithBraceTextAndNewMethod(); + + $this->insertMethodLastInClass($class, $method)->shouldReturn($result); + } + + private function getSingleMethodClass() + { + return <<beConstructedWith(false, false, true, false, false, false); + + $this->isReRunEnabled()->shouldReturn(true); + } + + public function it_says_rerun_is_not_enabled_when_setting_is_false() + { + $this->beConstructedWith(false, false, false, false, false, false); + + $this->isReRunEnabled()->shouldReturn(false); + } + + public function it_says_faking_is_enabled_when_setting_is_true() + { + $this->beConstructedWith(false, false, false, true, false, false); + + $this->isFakingEnabled()->shouldReturn(true); + } + + public function it_says_faking_is_not_enabled_when_setting_is_false() + { + $this->beConstructedWith(false, false, false, false, false, false); + + $this->isFakingEnabled()->shouldReturn(false); + } + + public function it_says_bootstrap_path_is_false_when_setting_is_false() + { + $this->beConstructedWith(false, false, false, false, false, false); + + $this->getBootstrapPath()->shouldReturn(false); + } + + public function it_returns_bootstrap_path_when_one_is_specified() + { + $this->beConstructedWith(false, false, false, false, '/path/to/file', false); + + $this->getBootstrapPath()->shouldReturn('/path/to/file'); + } + + public function it_returns_verbose_when_setting_is_true() + { + $this->beConstructedWith(false, false, false, false, false, true); + + $this->isVerbose()->shouldReturn(true); + } + + public function it_returns_verbose_when_setting_is_false() + { + $this->beConstructedWith(false, false, false, false, false, false); + + $this->isVerbose()->shouldReturn(false); + } +} diff --git a/spec/PhpSpec/Console/ApplicationSpec.php b/spec/PhpSpec/Console/ApplicationSpec.php new file mode 100644 index 0000000..6b7bd17 --- /dev/null +++ b/spec/PhpSpec/Console/ApplicationSpec.php @@ -0,0 +1,20 @@ +beConstructedWith('test'); + } + + public function it_is_initializable() + { + $this->shouldHaveType('PhpSpec\Console\Application'); + } +} diff --git a/spec/PhpSpec/Console/ConsoleIOSpec.php b/spec/PhpSpec/Console/ConsoleIOSpec.php new file mode 100644 index 0000000..a60026d --- /dev/null +++ b/spec/PhpSpec/Console/ConsoleIOSpec.php @@ -0,0 +1,283 @@ +isInteractive()->willReturn(true); + $input->getOption('no-code-generation')->willReturn(false); + $input->getOption('stop-on-failure')->willReturn(false); + + $config->isCodeGenerationEnabled()->willReturn(true); + $config->isStopOnFailureEnabled()->willReturn(false); + + $this->beConstructedWith($input, $output, $config, $prompter); + } + + public function it_has_io_interface() + { + $this->shouldHaveType('PhpSpec\IO\IO'); + } + + public function it_is_code_generation_ready_if_no_input_config_says_otherwise() + { + $this->isCodeGenerationEnabled()->shouldReturn(true); + } + + public function it_is_not_code_generation_ready_if_input_is_not_interactive($input) + { + $input->isInteractive()->willReturn(false); + + $this->isCodeGenerationEnabled()->shouldReturn(false); + } + + public function it_is_not_code_generation_ready_if_command_line_option_is_set($input) + { + $input->getOption('no-code-generation')->willReturn(true); + + $this->isCodeGenerationEnabled()->shouldReturn(false); + } + + public function it_is_not_code_generation_ready_if_config_option_is_set($config) + { + $config->isCodeGenerationEnabled()->willReturn(false); + + $this->isCodeGenerationEnabled()->shouldReturn(false); + } + + public function it_will_not_stop_on_failure_if_no_input_config_says_otherwise() + { + $this->isStopOnFailureEnabled()->shouldReturn(false); + } + + public function it_will_stop_on_failure_if_command_line_option_is_set($input) + { + $input->getOption('stop-on-failure')->willReturn(true); + + $this->isStopOnFailureEnabled()->shouldReturn(true); + } + + public function it_will_stop_on_failure_if_config_option_is_set($config) + { + $config->isStopOnFailureEnabled()->willReturn(true); + + $this->isStopOnFailureEnabled()->shouldReturn(true); + } + + public function it_will_enable_rerunning_if_command_line_option_is_not_set_and_config_doesnt_disallow($input, $config) + { + $input->getOption('no-rerun')->willReturn(false); + $config->isReRunEnabled()->willReturn(true); + + $this->isRerunEnabled()->shouldReturn(true); + } + + public function it_will_disable_rerunning_if_command_line_option_is_set($input, $config) + { + $input->getOption('no-rerun')->willReturn(true); + $config->isReRunEnabled()->willReturn(true); + + $this->isRerunEnabled()->shouldReturn(false); + } + + public function it_will_disable_rerunning_if_config_option_is_set($input, $config) + { + $input->getOption('no-rerun')->willReturn(false); + $config->isReRunEnabled()->willReturn(false); + + $this->isRerunEnabled()->shouldReturn(false); + } + + public function it_will_disable_faking_if_command_line_option_and_config_flag_are_not_set($input, $config) + { + $input->getOption('fake')->willReturn(false); + $config->isFakingEnabled()->willReturn(false); + + $this->isFakingEnabled()->shouldReturn(false); + } + + public function it_will_enable_faking_if_command_line_option_is_set($input, $config) + { + $input->getOption('fake')->willReturn(true); + $config->isFakingEnabled()->willReturn(false); + + $this->isFakingEnabled()->shouldReturn(true); + } + + public function it_will_enable_faking_if_config_flag_is_set($input, $config) + { + $input->getOption('fake')->willReturn(false); + $config->isFakingEnabled()->willReturn(true); + + $this->isFakingEnabled()->shouldReturn(true); + } + + public function it_will_report_no_bootstrap_when_there_is_none($input, $config) + { + $input->getOption('bootstrap')->willReturn(null); + $config->getBootstrapPath()->willReturn(false); + + $this->getBootstrapPath()->shouldReturn(null); + } + + public function it_will_report_bootstrap_path_when_one_is_in_the_config_file($input, $config) + { + $input->getOption('bootstrap')->willReturn(null); + $config->getBootstrapPath()->willReturn('/path/to/bootstrap.php'); + + $this->getBootstrapPath()->shouldReturn('/path/to/bootstrap.php'); + } + + public function it_will_report_bootstrap_path_when_one_is_specified_at_the_command_line($input, $config) + { + $input->getOption('bootstrap')->willReturn('/path/to/bootstrap.php'); + $config->getBootstrapPath()->willReturn(false); + + $this->getBootstrapPath()->shouldReturn('/path/to/bootstrap.php'); + } + + public function it_will_report_bootstrap_path_from_cli_when_different_paths_are_specified_in_config_and_cli($input, $config) + { + $input->getOption('bootstrap')->willReturn('/path/to/bootstrap.php'); + $config->getBootstrapPath()->willReturn('/path/to/different.php'); + + $this->getBootstrapPath()->shouldReturn('/path/to/bootstrap.php'); + } + + public function it_defaults_the_block_width() + { + $this->getBlockWidth()->shouldReturn(60); + } + + public function it_sets_the_block_width_to_the_minimum_when_terminal_is_narrow() + { + $this->setConsoleWidth(10); + + $this->getBlockWidth()->shouldReturn(60); + } + + public function it_sets_the_block_width_to_the_maximum_when_terminal_is_very_wide() + { + $this->setConsoleWidth(1000); + + $this->getBlockWidth()->shouldReturn(80); + } + + public function it_sets_the_block_width_to_narrower_than_the_terminal_width_when_terminal_is_in_range() + { + $this->setConsoleWidth(75); + + $this->getBlockWidth()->shouldReturn(65); + } + + public function it_writes_a_message_about_broken_code(OutputInterface $output) + { + $message = 'Error message'; + $output->writeln(' ')->shouldBeCalledTimes(2); + $output->writeln('Error message ')->shouldBeCalled(); + $output->writeln('')->shouldBeCalled(); + + $this->writeBrokenCodeBlock($message); + } + + public function it_wraps_long_broken_message(OutputInterface $output) + { + $message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pro maximus nulla eget libero rhoncus lacinia.'; + $output->writeln(' ')->shouldBeCalledTimes(2); + $output->writeln('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pro')->shouldBeCalled(); + $output->writeln('maximus nulla eget libero rhoncus lacinia. ')->shouldBeCalled(); + $output->writeln('')->shouldBeCalled(); + + $this->writeBrokenCodeBlock($message); + } + + public function it_indents_and_wraps_long_broken_message(OutputInterface $output) + { + $message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin maximus nulla eget libero rhoncus lacinia.'; + $output->writeln(' ')->shouldBeCalledTimes(2); + $output->writeln(' Lorem ipsum dolor sit amet, consectetur adipiscing elit. ')->shouldBeCalled(); + $output->writeln(' Proin maximus nulla eget libero rhoncus lacinia. ')->shouldBeCalled(); + $output->writeln('')->shouldBeCalled(); + + $this->writeBrokenCodeBlock($message, 2); + } + + public function it_will_report_verbose_if_config_flag_is_set_and_console_setted_to_quiet(OutputInterface $output, OptionsConfig $config) + { + $output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_QUIET); + $config->isVerbose()->willReturn(true); + + $this->isVerbose()->shouldReturn(true); + } + + public function it_will_report_verbose_if_config_flag_is_set_and_console_setted_to_normal( + OutputInterface $output, + OptionsConfig $config + ) { + $output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_NORMAL); + $config->isVerbose()->willReturn(true); + + $this->isVerbose()->shouldReturn(true); + } + + public function it_will_not_report_verbose_if_config_flag_is_not_set_and_console_setted_to_quiet( + OutputInterface $output, + OptionsConfig $config + ) { + $output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_QUIET); + $config->isVerbose()->willReturn(false); + + $this->isVerbose()->shouldReturn(false); + } + + public function it_will_not_report_verbose_if_config_flag_is_not_set_and_console_setted_to_normal( + OutputInterface $output, + OptionsConfig $config + ) { + $output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_NORMAL); + $config->isVerbose()->willReturn(false); + + $this->isVerbose()->shouldReturn(false); + } + + public function it_will_report_verbose_if_config_flag_is_not_set_but_console_setted_to_verbose( + OutputInterface $output, + OptionsConfig $config + ) { + $output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_VERBOSE); + $config->isVerbose()->willReturn(false); + + $this->isVerbose()->shouldReturn(true); + } + + public function it_will_report_verbose_if_config_flag_is_not_set_but_console_setted_to_very_verbose( + OutputInterface $output, + OptionsConfig $config + ) { + $output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_VERY_VERBOSE); + $config->isVerbose()->willReturn(false); + + $this->isVerbose()->shouldReturn(true); + } + + public function it_will_report_verbose_if_config_flag_is_not_set_but_console_setted_to_debug( + OutputInterface $output, + OptionsConfig $config + ) { + $output->getVerbosity()->willReturn(OutputInterface::VERBOSITY_DEBUG); + $config->isVerbose()->willReturn(false); + + $this->isVerbose()->shouldReturn(true); + } +} diff --git a/spec/PhpSpec/Console/Provider/NamespacesAutocompleteProviderSpec.php b/spec/PhpSpec/Console/Provider/NamespacesAutocompleteProviderSpec.php new file mode 100644 index 0000000..b5c999b --- /dev/null +++ b/spec/PhpSpec/Console/Provider/NamespacesAutocompleteProviderSpec.php @@ -0,0 +1,50 @@ +beConstructedWith($finder, [$locator]); + $locator->getFullSrcPath()->willReturn('/app/src'); + } + + public function it_returns_empty_array_if_nothing_found($finder) + { + $finder->files()->willReturn($finder); + $finder->name('*.php')->willReturn($finder); + $finder->in(['/app/src'])->willReturn([]); + + $this->getNamespaces()->shouldHaveCount(0); + } + + public function it_returns_namespaces_from_php_files( + $finder, + SplFileInfo $file1, + SplFileInfo $file2, + SplFileInfo $file3 + ) { + $finder->files()->shouldBeCalled()->willReturn($finder); + $finder->name('*.php')->shouldBeCalled()->willReturn($finder); + $finder->in(['/app/src'])->shouldBeCalled()->willReturn([$file1, $file2, $file3]); + + $file1->getContents()->willReturn('getContents()->willReturn('getContents()->willReturn('getNamespaces(); + + $namespaces->shouldHaveCount(3); + $namespaces->shouldContain('App\\'); + $namespaces->shouldContain('App\\Foo\\'); + $namespaces->shouldContain('App\\Bar\\'); + } +} diff --git a/spec/PhpSpec/Console/ResultConverterSpec.php b/spec/PhpSpec/Console/ResultConverterSpec.php new file mode 100644 index 0000000..d2d43c1 --- /dev/null +++ b/spec/PhpSpec/Console/ResultConverterSpec.php @@ -0,0 +1,36 @@ +convert(ExampleEvent::PASSED)->shouldReturn(0); + } + + public function it_converts_skipped_result_code_into_0() + { + $this->convert(ExampleEvent::SKIPPED)->shouldReturn(0); + } + + public function it_converts_pending_result_code_into_1() + { + $this->convert(ExampleEvent::PENDING)->shouldReturn(1); + } + + public function it_converts_failed_result_code_into_1() + { + $this->convert(ExampleEvent::FAILED)->shouldReturn(1); + } + + public function it_converts_broken_result_code_into_1() + { + $this->convert(ExampleEvent::BROKEN)->shouldReturn(1); + } +} diff --git a/spec/PhpSpec/Event/ExampleEventSpec.php b/spec/PhpSpec/Event/ExampleEventSpec.php new file mode 100644 index 0000000..09550b7 --- /dev/null +++ b/spec/PhpSpec/Event/ExampleEventSpec.php @@ -0,0 +1,74 @@ +beConstructedWith($example, 10, $this->FAILED, $exception); + + $example->getSpecification()->willReturn($specification); + $specification->getSuite()->willReturn($suite); + } + + public function it_is_an_event() + { + $this->shouldBeAnInstanceOf('Symfony\Component\EventDispatcher\Event'); + $this->shouldBeAnInstanceOf('PhpSpec\Event\PhpSpecEvent'); + } + + public function it_provides_a_link_to_example($example) + { + $this->getExample()->shouldReturn($example); + } + + public function it_provides_a_link_to_specification($specification) + { + $this->getSpecification()->shouldReturn($specification); + } + + public function it_provides_a_link_to_suite($suite) + { + $this->getSuite()->shouldReturn($suite); + } + + public function it_provides_a_link_to_time() + { + $this->getTime()->shouldReturn((double) 10.0); + } + + public function it_provides_a_link_to_result() + { + $this->getResult()->shouldReturn($this->FAILED); + } + + public function it_provides_a_link_to_exception($exception) + { + $this->getException()->shouldReturn($exception); + } + + public function it_initializes_a_default_result(ExampleNode $example) + { + $this->beConstructedWith($example); + + $this->getResult()->shouldReturn($this->PASSED); + } + + public function it_initializes_a_default_time(ExampleNode $example) + { + $this->beConstructedWith($example); + + $this->getTime()->shouldReturn((double) 0.0); + } +} diff --git a/spec/PhpSpec/Event/ExpectationEventSpec.php b/spec/PhpSpec/Event/ExpectationEventSpec.php new file mode 100644 index 0000000..3ac45d6 --- /dev/null +++ b/spec/PhpSpec/Event/ExpectationEventSpec.php @@ -0,0 +1,93 @@ +beConstructedWith($example, $matcher, $subject, $method, $arguments, $this->FAILED, $exception); + + $example->getSpecification()->willReturn($specification); + $specification->getSuite()->willReturn($suite); + } + + public function it_is_an_event() + { + $this->shouldBeAnInstanceOf('Symfony\Component\EventDispatcher\Event'); + $this->shouldBeAnInstanceOf('PhpSpec\Event\PhpSpecEvent'); + } + + public function it_provides_a_link_to_matcher($matcher) + { + $this->getMatcher()->shouldReturn($matcher); + } + + public function it_provides_a_link_to_example($example) + { + $this->getExample()->shouldReturn($example); + } + + public function it_provides_a_link_to_specification($specification) + { + $this->getSpecification()->shouldReturn($specification); + } + + public function it_provides_a_link_to_suite($suite) + { + $this->getSuite()->shouldReturn($suite); + } + + public function it_provides_a_link_to_subject($subject) + { + $this->getSubject()->shouldReturn($subject); + } + + public function it_provides_a_link_to_method() + { + $this->getMethod()->shouldReturn('calledMethod'); + } + + public function it_provides_a_link_to_arguments() + { + $this->getArguments()->shouldReturn(['methodArguments']); + } + + public function it_provides_a_link_to_result() + { + $this->getResult()->shouldReturn($this->FAILED); + } + + public function it_provides_a_link_to_exception($exception) + { + $this->getException()->shouldReturn($exception); + } + + public function it_initializes_a_default_result(ExampleNode $example, Matcher $matcher, $subject) + { + $method = 'calledMethod'; + $arguments = ['methodArguments']; + + $this->beConstructedWith($example, $matcher, $subject, $method, $arguments); + + $this->getResult()->shouldReturn($this->PASSED); + } +} diff --git a/spec/PhpSpec/Event/FileCreationEventSpec.php b/spec/PhpSpec/Event/FileCreationEventSpec.php new file mode 100644 index 0000000..75ca40e --- /dev/null +++ b/spec/PhpSpec/Event/FileCreationEventSpec.php @@ -0,0 +1,32 @@ +beConstructedWith($this->filepath); + } + + public function it_should_be_a_symfony_event() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\Event'); + } + + public function it_should_be_a_phpspec_event() + { + $this->shouldImplement('PhpSpec\Event\PhpSpecEvent'); + } + + public function it_should_return_the_created_file_path() + { + $this->getFilePath()->shouldReturn($this->filepath); + } +} diff --git a/spec/PhpSpec/Event/MethodCallEventSpec.php b/spec/PhpSpec/Event/MethodCallEventSpec.php new file mode 100644 index 0000000..fd9ed52 --- /dev/null +++ b/spec/PhpSpec/Event/MethodCallEventSpec.php @@ -0,0 +1,66 @@ +beConstructedWith($example, $subject, $method, $arguments, $returnValue); + + $example->getSpecification()->willReturn($specification); + $specification->getSuite()->willReturn($suite); + } + + public function it_is_an_event() + { + $this->shouldBeAnInstanceOf('Symfony\Component\EventDispatcher\Event'); + $this->shouldBeAnInstanceOf('PhpSpec\Event\PhpSpecEvent'); + } + + public function it_provides_a_link_to_example($example) + { + $this->getExample()->shouldReturn($example); + } + + public function it_provides_a_link_to_specification($specification) + { + $this->getSpecification()->shouldReturn($specification); + } + + public function it_provides_a_link_to_suite($suite) + { + $this->getSuite()->shouldReturn($suite); + } + + public function it_provides_a_link_to_subject($subject) + { + $this->getSubject()->shouldReturn($subject); + } + + public function it_provides_a_link_to_method() + { + $this->getMethod()->shouldReturn('calledMethod'); + } + + public function it_provides_a_link_to_arguments() + { + $this->getArguments()->shouldReturn(['methodArguments']); + } + + public function it_provides_a_link_to_return_value() + { + $this->getReturnValue()->shouldReturn('returned value'); + } +} diff --git a/spec/PhpSpec/Event/SpecificationEventSpec.php b/spec/PhpSpec/Event/SpecificationEventSpec.php new file mode 100644 index 0000000..a815f45 --- /dev/null +++ b/spec/PhpSpec/Event/SpecificationEventSpec.php @@ -0,0 +1,61 @@ +beConstructedWith($specification, 10, Example::FAILED); + + $specification->getSuite()->willReturn($suite); + } + + public function it_is_an_event() + { + $this->shouldBeAnInstanceOf('Symfony\Component\EventDispatcher\Event'); + $this->shouldBeAnInstanceOf('PhpSpec\Event\PhpSpecEvent'); + } + + public function it_provides_a_link_to_suite($suite) + { + $this->getSuite()->shouldReturn($suite); + } + + public function it_provides_a_link_to_specification($specification) + { + $this->getSpecification()->shouldReturn($specification); + } + + public function it_provides_a_link_to_time() + { + $this->getTime()->shouldReturn(10.0); + } + + public function it_provides_a_link_to_result() + { + $this->getResult()->shouldReturn(Example::FAILED); + } + + public function it_initializes_a_default_result(SpecificationNode $specification) + { + $this->beConstructedWith($specification); + + $this->getResult()->shouldReturn(Example::PASSED); + } + + public function it_initializes_a_default_time(SpecificationNode $specification) + { + $this->beConstructedWith($specification); + + $this->getTime()->shouldReturn((double) 0.0); + } +} diff --git a/spec/PhpSpec/Event/SuiteEventSpec.php b/spec/PhpSpec/Event/SuiteEventSpec.php new file mode 100644 index 0000000..b2d6cbf --- /dev/null +++ b/spec/PhpSpec/Event/SuiteEventSpec.php @@ -0,0 +1,72 @@ +beConstructedWith($suite, 10, Example::FAILED); + } + + public function it_is_an_event() + { + $this->shouldBeAnInstanceOf('Symfony\Component\EventDispatcher\Event'); + $this->shouldBeAnInstanceOf('PhpSpec\Event\PhpSpecEvent'); + } + + public function it_provides_a_link_to_suite($suite) + { + $this->getSuite()->shouldReturn($suite); + } + + public function it_provides_a_link_to_time() + { + $this->getTime()->shouldReturn(10.0); + } + + public function it_provides_a_link_to_result() + { + $this->getResult()->shouldReturn(Example::FAILED); + } + + public function it_defaults_to_saying_suite_is_not_worth_rerunning() + { + $this->isWorthRerunning()->shouldReturn(false); + } + + public function it_can_be_told_that_the_suite_is_worth_rerunning() + { + $this->markAsWorthRerunning(); + $this->isWorthRerunning()->shouldReturn(true); + } + + public function it_can_be_told_that_the_suite_is_no_longer_worth_rerunning() + { + $this->markAsWorthRerunning(); + $this->markAsNotWorthRerunning(); + + $this->isWorthRerunning()->shouldReturn(false); + } + + public function it_initializes_a_default_result(Suite $suite) + { + $this->beConstructedWith($suite); + + $this->getResult()->shouldReturn(Example::PASSED); + } + + public function it_initializes_a_default_time(Suite $suite) + { + $this->beConstructedWith($suite); + + $this->getTime()->shouldReturn((double) 0.0); + } +} diff --git a/spec/PhpSpec/Exception/ErrorExceptionSpec.php b/spec/PhpSpec/Exception/ErrorExceptionSpec.php new file mode 100644 index 0000000..f637c15 --- /dev/null +++ b/spec/PhpSpec/Exception/ErrorExceptionSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($this->error = new \Error('This is an error', 42)); + } + + public function it_is_an_exception() + { + $this->shouldHaveType(\Exception::class); + } + + public function its_message_is_the_same_as_the_errors() + { + $this->getMessage()->shouldEqual('This is an error'); + } + + public function its_code_is_the_same_as_the_errors() + { + $this->getCode()->shouldEqual(42); + } + + public function its_previous_is_the_error() + { + $this->getPrevious()->shouldEqual($this->error); + } + + public function its_line_is_the_same_as_the_errors() + { + $this->getLine()->shouldEqual($this->error->getLine()); + } +} diff --git a/spec/PhpSpec/Exception/Example/NotEqualExceptionSpec.php b/spec/PhpSpec/Exception/Example/NotEqualExceptionSpec.php new file mode 100644 index 0000000..6205b42 --- /dev/null +++ b/spec/PhpSpec/Exception/Example/NotEqualExceptionSpec.php @@ -0,0 +1,30 @@ +beConstructedWith('Not equal', 2, 5); + } + + public function it_is_failure() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Example\FailureException'); + } + + public function it_provides_a_link_to_expected() + { + $this->getExpected()->shouldReturn(2); + } + + public function it_provides_a_link_to_actual() + { + $this->getActual()->shouldReturn(5); + } +} diff --git a/spec/PhpSpec/Exception/Example/StopOnFailureExceptionSpec.php b/spec/PhpSpec/Exception/Example/StopOnFailureExceptionSpec.php new file mode 100644 index 0000000..6537b77 --- /dev/null +++ b/spec/PhpSpec/Exception/Example/StopOnFailureExceptionSpec.php @@ -0,0 +1,25 @@ +beConstructedWith('Message', 0, null, 1); + } + + public function it_is_an_example_exception() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Example\ExampleException'); + } + + public function it_has_a_the_result_of_the_last_spec() + { + $this->getResult()->shouldReturn(1); + } +} diff --git a/spec/PhpSpec/Exception/ExceptionFactorySpec.php b/spec/PhpSpec/Exception/ExceptionFactorySpec.php new file mode 100644 index 0000000..8e2ed51 --- /dev/null +++ b/spec/PhpSpec/Exception/ExceptionFactorySpec.php @@ -0,0 +1,174 @@ +beConstructedWith($presenter); + $this->fixture = new \stdClass(); + $this->fixture->subject = new \stdClass(); + $this->fixture->method = 'foo'; + $this->fixture->arguments = ['bar']; + $this->fixture->classname = '\stdClass'; + $this->fixture->property = 'zoo'; + } + + public function it_creates_a_named_constructor_not_found_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->classname}::{$this->fixture->method}") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->classname}::{$this->fixture->method}\""); + $this->fixture->message = 'Named constructor "\stdClass::foo" not found.'; + $this->createdException = $this->namedConstructorNotFound( + $this->fixture->classname, + $this->fixture->method, + $this->fixture->arguments + ); + + $this->shouldCreateNamedConstructorNotFoundException(); + } + + public function it_creates_a_method_not_found_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->classname}::{$this->fixture->method}") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->classname}::{$this->fixture->method}\""); + $this->fixture->message = 'Method "\stdClass::foo" not found.'; + $this->createdException = $this->methodNotFound( + $this->fixture->classname, + $this->fixture->method, + $this->fixture->arguments + ); + + $this->shouldCreateMethodNotFoundException(); + } + + public function it_creates_a_method_not_visible_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->classname}::{$this->fixture->method}") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->classname}::{$this->fixture->method}\""); + $this->fixture->message = 'Method "\stdClass::foo" not visible.'; + + $this->createdException = $this->methodNotVisible( + $this->fixture->classname, + $this->fixture->method, + $this->fixture->arguments + ); + + $this->shouldCreateMethodNotVisibleException(); + } + + public function it_creates_a_class_not_found_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->classname}") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->classname}\""); + $this->fixture->message = 'Class "\stdClass" does not exist.'; + $this->createdException = $this->classNotFound( + $this->fixture->classname + ); + + $this->shouldCreateClassNotFoundException(); + } + + public function it_creates_a_property_not_found_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->property}") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->property}\""); + $this->fixture->message = 'Property "zoo" not found.'; + $this->createdException = $this->propertyNotFound( + $this->fixture->subject, + $this->fixture->property + ); + + $this->shouldCreatePropertyNotFoundException(); + } + + public function it_creates_a_calling_method_on_non_object_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->method}()") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->method}()\""); + $fixtureMessage = "Call to a member function \"{$this->fixture->method}()\" on a non-object."; + $exception = $this->callingMethodOnNonObject($this->fixture->method); + $exception->shouldHaveType('PhpSpec\Exception\Wrapper\SubjectException'); + $exception->getMessage()->shouldBe($fixtureMessage); + } + + public function it_creates_a_setting_property_on_non_object_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->property}") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->property}\""); + $fixtureMessage = "Setting property \"{$this->fixture->property}\" on a non-object."; + $exception = $this->settingPropertyOnNonObject($this->fixture->property); + $exception->shouldHaveType('PhpSpec\Exception\Wrapper\SubjectException'); + $exception->getMessage()->shouldBe($fixtureMessage); + } + + public function it_creates_an_accessing_property_on_non_object_exception(Presenter $presenter) + { + $presenter->presentString("{$this->fixture->property}") + ->shouldBeCalled() + ->willReturn("\"{$this->fixture->property}\""); + $fixtureMessage = "Getting property \"{$this->fixture->property}\" on a non-object."; + $exception = $this->gettingPropertyOnNonObject($this->fixture->property); + $exception->shouldHaveType('PhpSpec\Exception\Wrapper\SubjectException'); + $exception->getMessage()->shouldBe($fixtureMessage); + } + + public function shouldCreateNamedConstructorNotFoundException() + { + $this->createdException->shouldHaveType('PhpSpec\Exception\Fracture\NamedConstructorNotFoundException'); + $this->createdException->getMessage()->shouldReturn($this->fixture->message); + $this->createdException->getSubject()->shouldBeLike($this->fixture->subject); + $this->createdException->getMethodName()->shouldReturn($this->fixture->method); + $this->createdException->getArguments()->shouldReturn($this->fixture->arguments); + } + + public function shouldCreateMethodNotFoundException() + { + $this->createdException->shouldHaveType('PhpSpec\Exception\Fracture\MethodNotFoundException'); + $this->createdException->getMessage()->shouldReturn($this->fixture->message); + $this->createdException->getSubject()->shouldBeLike($this->fixture->subject); + $this->createdException->getMethodName()->shouldReturn($this->fixture->method); + $this->createdException->getArguments()->shouldReturn($this->fixture->arguments); + } + + public function shouldCreateMethodNotVisibleException() + { + $this->createdException->shouldHaveType('PhpSpec\Exception\Fracture\MethodNotVisibleException'); + $this->createdException->getMessage()->shouldReturn($this->fixture->message); + $this->createdException->getSubject()->shouldBeLike($this->fixture->subject); + $this->createdException->getMethodName()->shouldReturn($this->fixture->method); + $this->createdException->getArguments()->shouldReturn($this->fixture->arguments); + } + + public function shouldCreateClassNotFoundException() + { + $this->createdException->shouldHaveType('PhpSpec\Exception\Fracture\ClassNotFoundException'); + $this->createdException->getMessage()->shouldReturn($this->fixture->message); + $this->createdException->getClassname()->shouldReturn($this->fixture->classname); + } + + public function shouldCreatePropertyNotFoundException() + { + $this->createdException->shouldHaveType('PhpSpec\Exception\Fracture\PropertyNotFoundException'); + $this->createdException->getMessage()->shouldReturn($this->fixture->message); + $this->createdException->getSubject()->shouldReturn($this->fixture->subject); + $this->createdException->getProperty()->shouldReturn($this->fixture->property); + } +} diff --git a/spec/PhpSpec/Exception/ExceptionSpec.php b/spec/PhpSpec/Exception/ExceptionSpec.php new file mode 100644 index 0000000..953999f --- /dev/null +++ b/spec/PhpSpec/Exception/ExceptionSpec.php @@ -0,0 +1,23 @@ +shouldBeAnInstanceOf('Exception'); + } + + public function it_could_have_a_cause(ReflectionMethod $cause) + { + $this->setCause($cause); + $this->getCause()->shouldReturn($cause); + } +} diff --git a/spec/PhpSpec/Exception/Fracture/ClassNotFoundExceptionSpec.php b/spec/PhpSpec/Exception/Fracture/ClassNotFoundExceptionSpec.php new file mode 100644 index 0000000..21d3543 --- /dev/null +++ b/spec/PhpSpec/Exception/Fracture/ClassNotFoundExceptionSpec.php @@ -0,0 +1,25 @@ +beConstructedWith('Not equal', 'stdClass'); + } + + public function it_is_fracture() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Fracture\FractureException'); + } + + public function it_provides_a_link_to_classname() + { + $this->getClassname()->shouldReturn('stdClass'); + } +} diff --git a/spec/PhpSpec/Exception/Fracture/InterfaceNotImplementedExceptionSpec.php b/spec/PhpSpec/Exception/Fracture/InterfaceNotImplementedExceptionSpec.php new file mode 100644 index 0000000..f66e59d --- /dev/null +++ b/spec/PhpSpec/Exception/Fracture/InterfaceNotImplementedExceptionSpec.php @@ -0,0 +1,30 @@ +beConstructedWith('Not equal', $subject, 'ArrayAccess'); + } + + public function it_is_fracture() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Fracture\FractureException'); + } + + public function it_provides_a_link_to_subject($subject) + { + $this->getSubject()->shouldReturn($subject); + } + + public function it_provides_a_link_to_interface() + { + $this->getInterface()->shouldReturn('ArrayAccess'); + } +} diff --git a/spec/PhpSpec/Exception/Fracture/MethodNotFoundExceptionSpec.php b/spec/PhpSpec/Exception/Fracture/MethodNotFoundExceptionSpec.php new file mode 100644 index 0000000..f42bffb --- /dev/null +++ b/spec/PhpSpec/Exception/Fracture/MethodNotFoundExceptionSpec.php @@ -0,0 +1,35 @@ +beConstructedWith('No method', $subject, 'setName', ['everzet']); + } + + public function it_is_fracture() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Fracture\FractureException'); + } + + public function it_provides_a_link_to_subject($subject) + { + $this->getSubject()->shouldReturn($subject); + } + + public function it_provides_a_link_to_methodName() + { + $this->getMethodName()->shouldReturn('setName'); + } + + public function it_provides_a_link_to_arguments() + { + $this->getArguments()->shouldReturn(['everzet']); + } +} diff --git a/spec/PhpSpec/Exception/Fracture/MethodNotVisibleExceptionSpec.php b/spec/PhpSpec/Exception/Fracture/MethodNotVisibleExceptionSpec.php new file mode 100644 index 0000000..564db43 --- /dev/null +++ b/spec/PhpSpec/Exception/Fracture/MethodNotVisibleExceptionSpec.php @@ -0,0 +1,35 @@ +beConstructedWith('No method', $subject, 'setName', ['everzet']); + } + + public function it_is_fracture() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Fracture\FractureException'); + } + + public function it_provides_a_link_to_subject($subject) + { + $this->getSubject()->shouldReturn($subject); + } + + public function it_provides_a_link_to_methodName() + { + $this->getMethodName()->shouldReturn('setName'); + } + + public function it_provides_a_link_to_arguments() + { + $this->getArguments()->shouldReturn(['everzet']); + } +} diff --git a/spec/PhpSpec/Exception/Fracture/NamedConstructorNotFoundExceptionSpec.php b/spec/PhpSpec/Exception/Fracture/NamedConstructorNotFoundExceptionSpec.php new file mode 100644 index 0000000..1a77ce3 --- /dev/null +++ b/spec/PhpSpec/Exception/Fracture/NamedConstructorNotFoundExceptionSpec.php @@ -0,0 +1,35 @@ +beConstructedWith('No named constructor', $subject, 'setName', ['jmurphy']); + } + + public function it_is_fracture() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Fracture\FractureException'); + } + + public function it_provides_a_link_to_subject($subject) + { + $this->getSubject()->shouldReturn($subject); + } + + public function it_provides_a_link_to_methodName() + { + $this->getMethodName()->shouldReturn('setName'); + } + + public function it_provides_a_link_to_arguments() + { + $this->getArguments()->shouldReturn(['jmurphy']); + } +} diff --git a/spec/PhpSpec/Exception/Fracture/PropertyNotFoundExceptionSpec.php b/spec/PhpSpec/Exception/Fracture/PropertyNotFoundExceptionSpec.php new file mode 100644 index 0000000..c41a5a0 --- /dev/null +++ b/spec/PhpSpec/Exception/Fracture/PropertyNotFoundExceptionSpec.php @@ -0,0 +1,30 @@ +beConstructedWith('No method', $subject, 'attributes'); + } + + public function it_is_fracture() + { + $this->shouldBeAnInstanceOf('PhpSpec\Exception\Fracture\FractureException'); + } + + public function it_provides_a_link_to_subject($subject) + { + $this->getSubject()->shouldReturn($subject); + } + + public function it_provides_a_link_to_property() + { + $this->getProperty()->shouldReturn('attributes'); + } +} diff --git a/spec/PhpSpec/Exception/Wrapper/InvalidCollaboratorTypeExceptionSpec.php b/spec/PhpSpec/Exception/Wrapper/InvalidCollaboratorTypeExceptionSpec.php new file mode 100644 index 0000000..9dd5acc --- /dev/null +++ b/spec/PhpSpec/Exception/Wrapper/InvalidCollaboratorTypeExceptionSpec.php @@ -0,0 +1,40 @@ +getName()->willReturn('bar'); + $this->beConstructedWith($parameter, $function); + } + + public function it_is_initializable() + { + $this->shouldHaveType('PhpSpec\Exception\Wrapper\InvalidCollaboratorTypeException'); + $this->shouldHaveType('PhpSpec\Exception\Wrapper\CollaboratorException'); + } + + public function it_generates_correct_message_based_on_function_and_parameter( + \ReflectionParameter $parameter, + \ReflectionMethod $function, + \ReflectionClass $class + ) { + $parameter->getPosition()->willReturn(2); + $function->getDeclaringClass()->willReturn($class); + $class->getName()->willReturn('Acme\Foo'); + $function->getName()->willReturn('bar'); + + $this->getMessage()->shouldStartWith('Collaborator must be an object: argument 2 defined in Acme\Foo::bar.'); + } + + public function it_sets_cause(\ReflectionFunction $function) + { + $this->getCause()->shouldReturn($function); + } +} diff --git a/spec/PhpSpec/Formatter/BasicFormatterSpec.php b/spec/PhpSpec/Formatter/BasicFormatterSpec.php new file mode 100644 index 0000000..68da92a --- /dev/null +++ b/spec/PhpSpec/Formatter/BasicFormatterSpec.php @@ -0,0 +1,44 @@ +beAnInstanceOf('spec\PhpSpec\Formatter\TestableBasicFormatter'); + $this->beConstructedWith($presenter, $io, $stats); + } + + public function it_is_an_event_subscriber() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_returns_a_list_of_subscribed_events() + { + $this::getSubscribedEvents()->shouldBe( + [ + 'beforeSuite' => 'beforeSuite', + 'afterSuite' => 'afterSuite', + 'beforeExample' => 'beforeExample', + 'afterExample' => 'afterExample', + 'beforeSpecification' => 'beforeSpecification', + 'afterSpecification' => 'afterSpecification' + ] + ); + } +} + +class TestableBasicFormatter extends BasicFormatter +{ +} diff --git a/spec/PhpSpec/Formatter/DotFormatterSpec.php b/spec/PhpSpec/Formatter/DotFormatterSpec.php new file mode 100644 index 0000000..5454b6a --- /dev/null +++ b/spec/PhpSpec/Formatter/DotFormatterSpec.php @@ -0,0 +1,205 @@ +beConstructedWith($presenter, $io, $stats); + $presenter->presentString(Argument::cetera())->willReturn('presented string'); + $presenter->presentException(Argument::cetera())->willReturn('presented exception'); + $io->askConfirmation(Argument::any())->willReturn(false); + $io->write(Argument::any())->should(function () { + return; + }); + $io->writeln(Argument::cetera())->should(function () { + return; + }); + $io->getBlockWidth()->willReturn(80); + $event->getTime()->willReturn(10.0); + } + + public function it_is_a_console_formatter() + { + $this->shouldHaveType('PhpSpec\Formatter\ConsoleFormatter'); + } + + public function it_outputs_a_dot_for_a_passed_example( + ExampleEvent $event, + ConsoleIO $io, + StatisticsCollector $stats + ) { + $event->getResult()->willReturn(ExampleEvent::PASSED); + $stats->getEventsCount()->willReturn(1); + + $this->afterExample($event); + + $io->write('.')->shouldHaveBeenCalled(); + } + + public function it_outputs_a_p_for_a_pending_example( + ExampleEvent $event, + ConsoleIO $io, + StatisticsCollector $stats + ) { + $event->getResult()->willReturn(ExampleEvent::PENDING); + $stats->getEventsCount()->willReturn(1); + + $this->afterExample($event); + + $io->write('P')->shouldHaveBeenCalled(); + } + + public function it_outputs_an_s_for_a_skipped_example( + ExampleEvent $event, + ConsoleIO $io, + StatisticsCollector $stats + ) { + $event->getResult()->willReturn(ExampleEvent::SKIPPED); + $stats->getEventsCount()->willReturn(1); + + $this->afterExample($event); + + $io->write('S')->shouldHaveBeenCalled(); + } + + public function it_outputs_an_f_for_a_failed_example( + ExampleEvent $event, + ConsoleIO $io, + StatisticsCollector $stats + ) { + $event->getResult()->willReturn(ExampleEvent::FAILED); + $stats->getEventsCount()->willReturn(1); + + $this->afterExample($event); + + $io->write('F')->shouldHaveBeenCalled(); + } + + public function it_outputs_a_b_for_a_broken_example( + ExampleEvent $event, + ConsoleIO $io, + StatisticsCollector $stats + ) { + $event->getResult()->willReturn(ExampleEvent::BROKEN); + $stats->getEventsCount()->willReturn(1); + + $this->afterExample($event); + + $io->write('B')->shouldHaveBeenCalled(); + } + + public function it_outputs_the_progress_every_50_examples( + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent, + ConsoleIO $io, + StatisticsCollector $stats, + Suite $suite + ) { + $exampleEvent->getResult()->willReturn(ExampleEvent::PASSED); + + $suiteEvent->getSuite()->willReturn($suite); + $suite->count()->willReturn(100); + + $stats->getEventsCount()->willReturn(50); + + $this->beforeSuite($suiteEvent); + $this->afterExample($exampleEvent); + + $io->write(' 50 / 100')->shouldHaveBeenCalled(); + } + + public function it_outputs_exceptions_for_failed_examples( + SuiteEvent $event, + ExampleEvent $pendingEvent, + ConsoleIO $io, + StatisticsCollector $stats, + SpecificationNode $specification, + ExampleNode $example + ) { + $example->getLineNumber()->willReturn(37); + $example->getTitle()->willReturn('it tests something'); + + $specification->getTitle()->willReturn('specification title'); + + $pendingEvent->getException()->willReturn(new PendingException()); + $pendingEvent->getSpecification()->willReturn($specification); + $pendingEvent->getExample()->willReturn($example); + + $io->isVerbose()->willReturn(false); + $io->getBlockWidth()->willReturn(10); + $io->write(Argument::type('string'))->should(function () { + }); + $io->writeln(Argument::cetera())->should(function () { + }); + + $stats->getEventsCount()->willReturn(1); + $stats->getFailedEvents()->willReturn([]); + $stats->getBrokenEvents()->willReturn([]); + $stats->getPendingEvents()->willReturn([$pendingEvent]); + $stats->getSkippedEvents()->willReturn([]); + $stats->getTotalSpecs()->willReturn(1); + + $stats->getCountsHash()->willReturn([ + 'passed' => 0, + 'pending' => 1, + 'skipped' => 0, + 'failed' => 0, + 'broken' => 0, + ]); + + $this->afterSuite($event); + + $expected = ' 37 - it tests something'; + $io->writeln($expected)->shouldHaveBeenCalled(); + } + + public function it_outputs_a_suite_summary( + SuiteEvent $event, + ConsoleIO $io, + StatisticsCollector $stats + ) { + $stats->getEventsCount()->willReturn(1); + $stats->getFailedEvents()->willReturn([]); + $stats->getBrokenEvents()->willReturn([]); + $stats->getPendingEvents()->willReturn([]); + $stats->getSkippedEvents()->willReturn([]); + $stats->getTotalSpecs()->willReturn(15); + $event->getTime()->willReturn(12.345); + + $stats->getCountsHash()->willReturn([ + 'passed' => 1, + 'pending' => 0, + 'skipped' => 0, + 'failed' => 2, + 'broken' => 0, + ]); + + $this->afterSuite($event); + + $io->writeln('15 specs')->shouldHaveBeenCalled(); + $io->writeln("\n12345ms")->shouldHaveBeenCalled(); + $io->write('1 example ')->shouldHaveBeenCalled(); + $expected = '(1 passed, 2 failed)'; + $io->write($expected)->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Formatter/Html/HtmlIOSpec.php b/spec/PhpSpec/Formatter/Html/HtmlIOSpec.php new file mode 100644 index 0000000..01e19ad --- /dev/null +++ b/spec/PhpSpec/Formatter/Html/HtmlIOSpec.php @@ -0,0 +1,17 @@ +beConstructedWith($input); + } +} diff --git a/spec/PhpSpec/Formatter/Html/ReportFailedItemSpec.php b/spec/PhpSpec/Formatter/Html/ReportFailedItemSpec.php new file mode 100644 index 0000000..71bc7bf --- /dev/null +++ b/spec/PhpSpec/Formatter/Html/ReportFailedItemSpec.php @@ -0,0 +1,47 @@ + 42, 'file' => '/some/path/to/SomeException.php'] + ]; + const BACKTRACE = '#42 /some/path/to/SomeException.php'; + const CODE = 'code'; + + public function let(Template $template, ExampleEvent $event, Presenter $presenter) + { + $this->beConstructedWith($template, $event, $presenter); + } + + public function it_writes_a_fail_message_for_a_failing_example(Template $template, ExampleEvent $event, Presenter $presenter) + { + $event->getTitle()->willReturn(self::EVENT_TITLE); + $event->getMessage()->willReturn(self::EVENT_MESSAGE); + $event->getBacktrace()->willReturn(self::$BACKTRACE); + $event->getException()->willReturn(new \Exception()); + $template->render(HtmlTemplate::DIR.'/Template/ReportFailed.html', [ + 'title' => self::EVENT_TITLE, + 'message' => self::EVENT_MESSAGE, + 'backtrace' => self::BACKTRACE, + 'code' => self::CODE, + 'index' => 1, + 'specification' => 1 + ])->shouldBeCalled(); + $presenter->presentException(Argument::cetera())->willReturn(self::CODE); + $this->write(1); + } +} diff --git a/spec/PhpSpec/Formatter/Html/ReportItemFactorySpec.php b/spec/PhpSpec/Formatter/Html/ReportItemFactorySpec.php new file mode 100644 index 0000000..6531e0d --- /dev/null +++ b/spec/PhpSpec/Formatter/Html/ReportItemFactorySpec.php @@ -0,0 +1,43 @@ +beConstructedWith($template); + } + + public function it_creates_a_ReportPassedItem(ExampleEvent $event, Presenter $presenter) + { + $event->getResult()->willReturn(ExampleEvent::PASSED); + $this->create($event, $presenter)->shouldHaveType('PhpSpec\Formatter\Html\ReportPassedItem'); + } + + public function it_creates_a_ReportPendingItem(ExampleEvent $event, Presenter $presenter) + { + $event->getResult()->willReturn(ExampleEvent::PENDING); + $this->create($event, $presenter)->shouldHaveType('PhpSpec\Formatter\Html\ReportPendingItem'); + } + + public function it_creates_a_ReportFailedItem(ExampleEvent $event, Presenter $presenter) + { + $event->getResult()->willReturn(ExampleEvent::FAILED); + $this->create($event, $presenter)->shouldHaveType('PhpSpec\Formatter\Html\ReportFailedItem'); + } + + public function it_creates_a_ReportBrokenItem(ExampleEvent $event, Presenter $presenter) + { + $event->getResult()->willReturn(ExampleEvent::BROKEN); + $this->create($event, $presenter)->shouldHaveType('PhpSpec\Formatter\Html\ReportFailedItem'); + } +} diff --git a/spec/PhpSpec/Formatter/Html/ReportPassedItemSpec.php b/spec/PhpSpec/Formatter/Html/ReportPassedItemSpec.php new file mode 100644 index 0000000..79890af --- /dev/null +++ b/spec/PhpSpec/Formatter/Html/ReportPassedItemSpec.php @@ -0,0 +1,30 @@ +beConstructedWith($template, $event); + } + + public function it_writes_a_pass_message_for_a_passing_example(Template $template, ExampleEvent $event) + { + $event->getTitle()->willReturn(self::EVENT_TITLE); + $template->render(HtmlTemplate::DIR.'/Template/ReportPass.html', [ + 'title' => self::EVENT_TITLE + ])->shouldBeCalled(); + $this->write(); + } +} diff --git a/spec/PhpSpec/Formatter/Html/ReportPendingItemSpec.php b/spec/PhpSpec/Formatter/Html/ReportPendingItemSpec.php new file mode 100644 index 0000000..eb48548 --- /dev/null +++ b/spec/PhpSpec/Formatter/Html/ReportPendingItemSpec.php @@ -0,0 +1,31 @@ +beConstructedWith($template, $event); + } + + public function it_writes_a_pass_message_for_a_passing_example(Template $template, ExampleEvent $event) + { + $event->getTitle()->willReturn(self::EVENT_TITLE); + $template->render(HtmlTemplate::DIR.'/Template/ReportPending.html', [ + 'title' => self::EVENT_TITLE, + 'pendingExamplesCount' => 1 + ])->shouldBeCalled(); + $this->write(); + } +} diff --git a/spec/PhpSpec/Formatter/Html/TemplateSpec.php b/spec/PhpSpec/Formatter/Html/TemplateSpec.php new file mode 100644 index 0000000..4bdaa08 --- /dev/null +++ b/spec/PhpSpec/Formatter/Html/TemplateSpec.php @@ -0,0 +1,58 @@ +beConstructedWith($io); + } + + public function it_renders_the_string_as_is($io) + { + $this->render('text'); + + $io->write('text')->shouldHaveBeenCalled(); + } + + public function it_renders_a_variable($io) + { + $this->render('hello {name}', ['name' => 'Chuck Norris']); + $io->write('hello Chuck Norris')->shouldHaveBeenCalled(); + } + + public function it_works_for_many_instances_of_vars($io) + { + $this->render('{name}! {greeting}, {name}', [ + 'name' => 'Chuck', + 'greeting' => 'hello' + ]); + $io->write('Chuck! hello, Chuck')->shouldHaveBeenCalled(); + } + + public function it_renders_a_file($io) + { + $tempFile = __DIR__.'/_files/TemplateRenderFixture.tpl'; + mkdir(__DIR__.'/_files'); + file_put_contents($tempFile, 'hello, {name}'); + + $this->render($tempFile, ['name' => 'Chuck']); + + $io->write('hello, Chuck')->shouldHaveBeenCalled(); + } + + public function letgo() + { + if (file_exists(__DIR__.'/_files/TemplateRenderFixture.tpl')) { + unlink(__DIR__.'/_files/TemplateRenderFixture.tpl'); + rmdir(__DIR__.'/_files'); + } + } +} diff --git a/spec/PhpSpec/Formatter/HtmlFormatterSpec.php b/spec/PhpSpec/Formatter/HtmlFormatterSpec.php new file mode 100644 index 0000000..17ea8c5 --- /dev/null +++ b/spec/PhpSpec/Formatter/HtmlFormatterSpec.php @@ -0,0 +1,41 @@ +beConstructedWith($factory, $presenter, $io, $stats); + } + + public function it_is_an_event_subscriber() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_delegates_the_reporting_to_the_event_type_line_reporter( + ExampleEvent $event, + ReportItem $item, + ReportItemFactory $factory, + Presenter $presenter + ) { + $factory->create($event, $presenter)->willReturn($item); + $item->write(Argument::any())->shouldBeCalled(); + $this->afterExample($event); + } +} diff --git a/spec/PhpSpec/Formatter/JUnitFormatterSpec.php b/spec/PhpSpec/Formatter/JUnitFormatterSpec.php new file mode 100644 index 0000000..fb56f88 --- /dev/null +++ b/spec/PhpSpec/Formatter/JUnitFormatterSpec.php @@ -0,0 +1,227 @@ +beConstructedWith($presenter, $io, $stats); + } + + public function it_is_an_event_subscriber() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_stores_a_testcase_node_after_passed_example_run( + ExampleEvent $event, + SpecificationNode $specification, + \ReflectionClass $refClass + ) { + $event->getResult()->willReturn(ExampleEvent::PASSED); + $event->getTitle()->willReturn('example title'); + $event->getTime()->willReturn(1337); + $event->getSpecification()->willReturn($specification); + $specification->getClassReflection()->willReturn($refClass); + $refClass->getName()->willReturn('Acme\Foo\Bar'); + + $this->afterExample($event); + + $this->getTestCaseNodes()->shouldReturn([ + '' + ]); + } + + public function it_stores_a_testcase_node_after_broken_example_run( + ExampleEvent $event, + SpecificationNode $specification, + \ReflectionClass $refClass + ) { + $event->getResult()->willReturn(ExampleEvent::BROKEN); + $event->getTitle()->willReturn('example title'); + $event->getTime()->willReturn(1337); + + $event->getException()->willReturn(new ExceptionStub('Something went wrong', 'Exception trace')); + + $event->getSpecification()->willReturn($specification); + $specification->getClassReflection()->willReturn($refClass); + $refClass->getName()->willReturn('Acme\Foo\Bar'); + + $this->afterExample($event); + + $this->getTestCaseNodes()->shouldReturn([ + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + '' + ]); + } + + public function it_stores_a_testcase_node_after_failed_example_run( + ExampleEvent $event, + SpecificationNode $specification, + \ReflectionClass $refClass + ) { + $event->getResult()->willReturn(ExampleEvent::FAILED); + $event->getTitle()->willReturn('example title'); + $event->getTime()->willReturn(1337); + + $event->getException()->willReturn(new ExceptionStub('Something went wrong', 'Exception trace')); + + $event->getSpecification()->willReturn($specification); + $specification->getClassReflection()->willReturn($refClass); + $refClass->getName()->willReturn('Acme\Foo\Bar'); + + $this->afterExample($event); + + $this->getTestCaseNodes()->shouldReturn([ + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + '' + ]); + } + + public function it_stores_a_testcase_node_after_skipped_example_run( + ExampleEvent $event, + SpecificationNode $specification, + \ReflectionClass $refClass + ) { + $event->getResult()->willReturn(ExampleEvent::SKIPPED); + $event->getTitle()->willReturn('example title'); + $event->getTime()->willReturn(1337); + + $event->getException()->willReturn(new SkippingException('zog zog')); + + $event->getSpecification()->willReturn($specification); + $specification->getClassReflection()->willReturn($refClass); + $refClass->getName()->willReturn('Acme\Foo\Bar'); + + $this->afterExample($event); + + // skipped tag is escaped because a skipped tag is also registered in the console formatter + $this->getTestCaseNodes()->shouldReturn([ + ''."\n". + '\\'."\n". + '' + ]); + } + + public function it_aggregates_testcase_nodes_and_store_them_after_specification_run(SpecificationEvent $event) + { + $event->getTitle()->willReturn('specification title'); + $event->getTime()->willReturn(42); + + $this->setTestCaseNodes([ + '', + '', + '', + ]); + + $this->setExampleStatusCounts([ + ExampleEvent::FAILED => 1, + ExampleEvent::BROKEN => 2, + ExampleEvent::PENDING => 5, + ExampleEvent::SKIPPED => 3, + ]); + $this->afterSpecification($event); + + $this->getTestSuiteNodes()->shouldReturn([ + ''."\n". + ''."\n". + ''."\n". + ''."\n". + '' + ]); + $this->getTestCaseNodes()->shouldHaveCount(0); + $this->getExampleStatusCounts()->shouldReturn([ + ExampleEvent::PASSED => 0, + ExampleEvent::PENDING => 0, + ExampleEvent::SKIPPED => 0, + ExampleEvent::FAILED => 0, + ExampleEvent::BROKEN => 0, + ]); + } + + public function it_aggregates_testsuite_nodes_and_display_them_after_suite_run(SuiteEvent $event, $io, $stats) + { + $event->getTime()->willReturn(48151.62342); + $stats->getFailedEvents()->willReturn(range(1, 12)); + $stats->getBrokenEvents()->willReturn(range(1, 3)); + $stats->getEventsCount()->willReturn(100); + + $this->setTestSuiteNodes([ + ''."\n". + ''."\n". + ''."\n". + ''."\n". + '', + ''."\n". + ''."\n". + ''."\n". + '' + ]); + $this->afterSuite($event); + + $io->write( + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + '' + )->shouldBeCalled(); + } +} + +class ExceptionStub +{ + protected $trace; + protected $message; + + public function __construct($message, $trace) + { + $this->message = $message; + $this->trace = $trace; + } + + public function getMessage() + { + return $this->message; + } + + public function getTraceAsString() + { + return $this->trace; + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Differ/ArrayEngineSpec.php b/spec/PhpSpec/Formatter/Presenter/Differ/ArrayEngineSpec.php new file mode 100644 index 0000000..2defdf2 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Differ/ArrayEngineSpec.php @@ -0,0 +1,25 @@ +shouldBeAnInstanceOf('PhpSpec\Formatter\Presenter\Differ\DifferEngine'); + } + + public function it_supports_arrays() + { + $this->supports([], [1, 2, 3])->shouldReturn(true); + } + + public function it_does_not_support_anything_else() + { + $this->supports('str', 2)->shouldReturn(false); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Differ/DifferSpec.php b/spec/PhpSpec/Formatter/Presenter/Differ/DifferSpec.php new file mode 100644 index 0000000..60d22d4 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Differ/DifferSpec.php @@ -0,0 +1,44 @@ +supports('string1', 'string2')->willReturn(true); + $engine2->supports('string1', 'string2')->willReturn(false); + $engine1->compare('string1', 'string2')->willReturn('string1 !== string2'); + + $engine1->supports(2, 1)->willReturn(false); + $engine2->supports(2, 1)->willReturn(true); + $engine2->compare(2, 1)->willReturn('2 !== 1'); + + $this->addEngine($engine1); + $this->addEngine($engine2); + + $this->compare('string1', 'string2')->shouldReturn('string1 !== string2'); + $this->compare(2, 1)->shouldReturn('2 !== 1'); + } + + public function it_returns_null_if_engine_not_found() + { + $this->compare(1, 2)->shouldReturn(null); + } + + public function its_constructor_allows_a_list_of_engines(DifferEngine $engine) + { + $this->beConstructedWith([$engine]); + $engine->supports('string1', 'string2')->willReturn(true); + $engine->compare('string1', 'string2')->willReturn('string1 !== string2'); + + $this->compare('string1', 'string2')->shouldReturn('string1 !== string2'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Differ/ObjectEngineSpec.php b/spec/PhpSpec/Formatter/Presenter/Differ/ObjectEngineSpec.php new file mode 100644 index 0000000..1954baa --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Differ/ObjectEngineSpec.php @@ -0,0 +1,45 @@ +beConstructedWith($exporter, $stringDiffer); + } + + public function it_is_a_differ_engine() + { + $this->shouldHaveType('PhpSpec\Formatter\Presenter\Differ\DifferEngine'); + } + + public function it_does_not_support_scalars() + { + $this->supports(1, 2)->shouldReturn(false); + } + + public function it_only_supports_objects() + { + $this->supports(new \stdClass(), new \stdClass())->shouldReturn(true); + } + + public function it_converts_objects_to_string_and_diffs_the_result(Exporter $exporter, StringEngine $stringDiffer) + { + $exporter->export(Argument::type('DateTime'))->willReturn('DateTime'); + $exporter->export(Argument::type('ArrayObject'))->willReturn('ArrayObject'); + + $stringDiffer->compare('DateTime', 'ArrayObject')->willReturn('-DateTime+ArrayObject'); + + $diff = $this->compare(new \DateTime(), new \ArrayObject()); + + $diff->shouldBe('-DateTime+ArrayObject'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Differ/StringEngineSpec.php b/spec/PhpSpec/Formatter/Presenter/Differ/StringEngineSpec.php new file mode 100644 index 0000000..2b04aee --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Differ/StringEngineSpec.php @@ -0,0 +1,48 @@ +shouldBeAnInstanceOf('PhpSpec\Formatter\Presenter\Differ\DifferEngine'); + } + + public function it_supports_string_values() + { + $this->supports('string1', 'string2')->shouldReturn(true); + } + + public function it_calculates_strings_diff() + { + $expected = << +@@ -1,1 +1,1 @@ +-string1 ++string2 + +DIFF; + + $this->compare('string1', 'string2')->shouldBeEqualRegardlessOfLineEndings($expected); + } + + public function getMatchers(): array + { + return [ + 'beEqualRegardlessOfLineEndings' => function ($actual, $expected) { + $actual = str_replace("\r", '', $actual); + if ($actual !== $expected) { + throw new FailureException('Strings are not equal.'); + } + + return true; + } + ]; + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Exception/CallArgumentsPresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Exception/CallArgumentsPresenterSpec.php new file mode 100644 index 0000000..21bddad --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Exception/CallArgumentsPresenterSpec.php @@ -0,0 +1,73 @@ +beConstructedWith($differ); + } + + public function it_should_return_empty_string_if_there_are_no_method_prophecies( + UnexpectedCallException $exception, + ObjectProphecy $objectProphecy + ) { + $exception->getObjectProphecy()->willReturn($objectProphecy); + $exception->getArguments()->shouldBeCalled(); + $exception->getMethodName()->willReturn('method'); + + $objectProphecy->getMethodProphecies('method')->willReturn([]); + + $this->presentDifference($exception)->shouldReturn(''); + } + + public function it_should_return_empty_string_if_method_prophecies_all_contain_calls( + UnexpectedCallException $exception, + ObjectProphecy $objectProphecy, + MethodProphecy $prophecy, + Call $call, + ArgumentsWildcard $wildcard + ) { + $exception->getObjectProphecy()->willReturn($objectProphecy); + $exception->getArguments()->shouldBeCalled(); + $exception->getMethodName()->willReturn('method'); + + $objectProphecy->getMethodProphecies(Argument::any())->willReturn([$prophecy]); + $objectProphecy->findProphecyMethodCalls('method', $wildcard)->willReturn([$call]); + + $prophecy->getArgumentsWildcard()->willReturn($wildcard); + + $this->presentDifference($exception)->shouldReturn(''); + } + + public function it_should_return_empty_string_if_argument_counts_do_not_match( + UnexpectedCallException $exception, + ObjectProphecy $objectProphecy, + MethodProphecy $prophecy, + ArgumentsWildcard $wildcard + ) { + $exception->getObjectProphecy()->willReturn($objectProphecy); + $exception->getArguments()->willReturn(['a', 'b']); + $exception->getMethodName()->shouldBeCalled(); + + $objectProphecy->getMethodProphecies(Argument::any())->willReturn([$prophecy]); + $objectProphecy->findProphecyMethodCalls(Argument::any(), Argument::any())->willReturn([]); + + $prophecy->getArgumentsWildcard()->willReturn($wildcard); + $wildcard->getTokens()->willReturn(['a']); + + $this->presentDifference($exception)->shouldReturn(''); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Exception/GenericPhpSpecExceptionPresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Exception/GenericPhpSpecExceptionPresenterSpec.php new file mode 100644 index 0000000..d3aa3f8 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Exception/GenericPhpSpecExceptionPresenterSpec.php @@ -0,0 +1,21 @@ +beConstructedWith($elementPresenter); + } + + public function it_is_a_phpspec_exception_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Exception\PhpSpecExceptionPresenter'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Exception/HtmlPhpSpecExceptionPresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Exception/HtmlPhpSpecExceptionPresenterSpec.php new file mode 100644 index 0000000..041e992 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Exception/HtmlPhpSpecExceptionPresenterSpec.php @@ -0,0 +1,15 @@ +shouldImplement('PhpSpec\Formatter\Presenter\Exception\PhpSpecExceptionPresenter'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionElementPresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionElementPresenterSpec.php new file mode 100644 index 0000000..fc7f96c --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionElementPresenterSpec.php @@ -0,0 +1,81 @@ +beConstructedWith($typePresenter, $valuePresenter); + } + + public function it_is_an_exception_element_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Exception\ExceptionElementPresenter'); + } + + public function it_should_return_a_simple_exception_thrown_message( + ExceptionTypePresenter $typePresenter, + \Exception $exception + ) { + $typePresenter->present($exception)->willReturn('exc'); + $this->presentExceptionThrownMessage($exception)->shouldReturn('Exception exc has been thrown.'); + } + + public function it_should_present_a_code_line() + { + $this->presentCodeLine('3', '4')->shouldReturn('3 4'); + } + + public function it_should_present_a_highlighted_line_unchanged() + { + $this->presentHighlight('foo')->shouldReturn('foo'); + } + + public function it_should_present_the_header_of_an_exception_trace_unchanged() + { + $this->presentExceptionTraceHeader('foo')->shouldReturn('foo'); + } + + public function it_should_present_every_argument_in_an_exception_trace_method_as_a_value(ValuePresenter $valuePresenter) + { + $args = ['foo', 42]; + $valuePresenter->presentValue('foo')->shouldBeCalled(); + $valuePresenter->presentValue(42)->shouldBeCalled(); + + $this->presentExceptionTraceMethod('', '', '', $args); + } + + public function it_should_present_an_exception_trace_method(ValuePresenter $valuePresenter) + { + $valuePresenter->presentValue('a')->willReturn('zaz'); + $valuePresenter->presentValue('b')->willReturn('zbz'); + + $this->presentExceptionTraceMethod('class', 'type', 'method', ['a', 'b']) + ->shouldReturn(' classtypemethod(zaz, zbz)'); + } + + public function it_should_present_every_argument_in_an_exception_trace_function_as_a_value(ValuePresenter $valuePresenter) + { + $args = ['foo', 42]; + $valuePresenter->presentValue('foo')->shouldBeCalled(); + $valuePresenter->presentValue(42)->shouldBeCalled(); + + $this->presentExceptionTraceFunction('', $args); + } + + public function it_should_present_an_exception_trace_function(ValuePresenter $valuePresenter) + { + $valuePresenter->presentValue('a')->willReturn('zaz'); + $valuePresenter->presentValue('b')->willReturn('zbz'); + + $this->presentExceptionTraceFunction('function', ['a', 'b']) + ->shouldReturn(' function(zaz, zbz)'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionPresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionPresenterSpec.php new file mode 100644 index 0000000..58afa98 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Exception/SimpleExceptionPresenterSpec.php @@ -0,0 +1,33 @@ +beConstructedWith( + $differ, + $exceptionElementPresenter, + $callArgumentsPresenter, + $phpspecExceptionPresenter + ); + } + + public function it_is_an_exception_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Exception\ExceptionPresenter'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Exception/TaggingExceptionElementPresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Exception/TaggingExceptionElementPresenterSpec.php new file mode 100644 index 0000000..b229b9e --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Exception/TaggingExceptionElementPresenterSpec.php @@ -0,0 +1,67 @@ +beConstructedWith($exceptionTypePresenter, $valuePresenter); + } + + public function it_is_an_exception_element_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Exception\ExceptionElementPresenter'); + } + + public function it_should_tag_an_exception_thrown_message( + ExceptionTypePresenter $exceptionTypePresenter, + \Exception $exception + ) { + $exceptionTypePresenter->present($exception)->willReturn('exc'); + $this->presentExceptionThrownMessage($exception)->shouldReturn('Exception has been thrown.'); + } + + public function it_should_present_a_tagged_code_line() + { + $this->presentCodeLine('3', 'foo')->shouldReturn('3 foo'); + } + + public function it_should_present_a_tagged_highlighted_line() + { + $this->presentHighlight('foo')->shouldReturn('foo'); + } + + public function it_should_present_a_tagged_header_of_an_exception_trace() + { + $this->presentExceptionTraceHeader('foo')->shouldReturn('foo'); + } + + public function it_should_present_a_tagged_exception_trace_method(ValuePresenter $valuePresenter) + { + $valuePresenter->presentValue('a')->willReturn('zaz'); + $valuePresenter->presentValue('b')->willReturn('zbz'); + + $result = ' classtype'. + 'method(zaz, zbz)'; + + $this->presentExceptionTraceMethod('class', 'type', 'method', ['a', 'b'])->shouldReturn($result); + } + + public function it_should_present_a_tagged_exception_trace_function(ValuePresenter $valuePresenter) + { + $valuePresenter->presentValue('a')->willReturn('zaz'); + $valuePresenter->presentValue('b')->willReturn('zbz'); + + $result = ' function'. + '(zaz, zbz)'; + + $this->presentExceptionTraceFunction('function', ['a', 'b'])->shouldReturn($result); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/SimplePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/SimplePresenterSpec.php new file mode 100644 index 0000000..af153be --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/SimplePresenterSpec.php @@ -0,0 +1,55 @@ + + * (c) Konstantin Kudryashov + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace spec\PhpSpec\Formatter\Presenter; + +use PhpSpec\Formatter\Presenter\Exception\ExceptionPresenter; +use PhpSpec\Formatter\Presenter\Value\ValuePresenter; +use PhpSpec\ObjectBehavior; + +class SimplePresenterSpec extends ObjectBehavior +{ + public function let(ValuePresenter $valuePresenter, ExceptionPresenter $exceptionPresenter) + { + $this->beConstructedWith($valuePresenter, $exceptionPresenter); + } + + public function it_is_a_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Presenter'); + } + + public function it_returns_a_string_unchanged() + { + $message = 'this is a string'; + $this->presentString($message)->shouldReturn($message); + } + + public function it_should_be_a_proxy_for_an_exception_presenter( + ExceptionPresenter $exceptionPresenter, + \Exception $exception + ) { + $result = 'this is the result'; + $exceptionPresenter->presentException($exception, true)->willReturn($result); + $this->presentException($exception, true)->shouldReturn($result); + } + + public function it_should_be_a_proxy_for_a_value_presenter( + ValuePresenter $valuePresenter + ) { + $valuePresenter->presentValue('foo')->willReturn('zfooz'); + $this->presentValue('foo')->shouldReturn('zfooz'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/TaggingPresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/TaggingPresenterSpec.php new file mode 100644 index 0000000..1da0d48 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/TaggingPresenterSpec.php @@ -0,0 +1,40 @@ +beConstructedWith($presenter); + } + + public function it_is_a_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Presenter'); + } + + public function it_should_tag_strings() + { + $this->presentString('foo')->shouldReturn('foo'); + } + + public function it_should_tag_values_from_the_decorated_presenter(Presenter $presenter) + { + $presenter->presentValue('foo')->willReturn('zfooz'); + $this->presentValue('foo')->shouldReturn('zfooz'); + } + + public function it_should_return_presented_exceptions_from_the_decorated_presenter_unchanged( + Presenter $presenter, + \Exception $exception + ) { + $presenter->presentException($exception, true)->willReturn('exc'); + $this->presentException($exception, true)->shouldReturn('exc'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/ArrayTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/ArrayTypePresenterSpec.php new file mode 100644 index 0000000..eaad9d3 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/ArrayTypePresenterSpec.php @@ -0,0 +1,30 @@ +shouldImplement('PhpSpec\Formatter\Presenter\Value\TypePresenter'); + } + + public function it_should_support_array_values() + { + $this->supports([])->shouldReturn(true); + } + + public function it_should_present_an_empty_array_as_a_string() + { + $this->present([])->shouldReturn('[array:0]'); + } + + public function it_should_present_a_populated_array_as_a_string() + { + $this->present(['a', 'b'])->shouldReturn('[array:2]'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/BaseExceptionTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/BaseExceptionTypePresenterSpec.php new file mode 100644 index 0000000..6437d08 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/BaseExceptionTypePresenterSpec.php @@ -0,0 +1,33 @@ +shouldImplement('PhpSpec\Formatter\Presenter\Value\ExceptionTypePresenter'); + } + + public function it_should_support_exceptions() + { + $this->supports(new \Exception())->shouldReturn(true); + } + + public function it_should_present_an_exception_as_a_string() + { + $this->present(new \Exception('foo')) + ->shouldReturn('[exc:Exception("foo")]'); + } + + public function it_should_present_an_error_as_a_string() + { + $this->present(new ErrorException(new \Error('foo'))) + ->shouldReturn('[err:Error("foo")]'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/BooleanTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/BooleanTypePresenterSpec.php new file mode 100644 index 0000000..ecd36bc --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/BooleanTypePresenterSpec.php @@ -0,0 +1,31 @@ +shouldImplement('PhpSpec\Formatter\Presenter\Value\TypePresenter'); + } + + public function it_should_support_boolean_values() + { + $this->supports(true)->shouldReturn(true); + $this->supports(false)->shouldReturn(true); + } + + public function it_should_present_a_true_boolean_as_a_string() + { + $this->present(true)->shouldReturn('true'); + } + + public function it_should_present_a_false_boolean_as_a_string() + { + $this->present(false)->shouldReturn('false'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/CallableTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/CallableTypePresenterSpec.php new file mode 100644 index 0000000..384c471 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/CallableTypePresenterSpec.php @@ -0,0 +1,117 @@ +beConstructedWith($presenter); + } + + public function it_is_a_type_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Value\TypePresenter'); + } + + public function it_should_support_callable_values() + { + $this->supports(function () { + })->shouldReturn(true); + } + + public function it_should_present_a_closure() + { + $this->present(function () { + })->shouldReturn('[closure]'); + } + + public function it_should_present_function_callable_as_string() + { + $this->present('date')->shouldReturn('[date()]'); + } + + public function it_should_present_a_method_as_string( + WithMethod $object, + Presenter $presenter + ) { + $className = get_class($object->getWrappedObject()); + + $presenter->presentValue($object->getWrappedObject())->willReturn(sprintf('[obj:%s]', $className)); + + $this->present([$object, 'specMethod']) + ->shouldReturn(sprintf('[obj:%s]::specMethod()', $className)); + } + + public function it_should_present_a_magic_method_as_string( + WithMagicCall $object, + Presenter $presenter + ) { + $className = get_class($object->getWrappedObject()); + + $presenter->presentValue($object->getWrappedObject())->willReturn(sprintf('[obj:%s]', $className)); + + $this->present([$object, 'undefinedMethod']) + ->shouldReturn(sprintf('[obj:%s]::undefinedMethod()', $className)); + } + + public function it_should_present_a_static_method_as_string(WithMethod $object) + { + $className = get_class($object->getWrappedObject()); + $this->present([$className, 'specMethod']) + ->shouldReturn(sprintf('%s::specMethod()', $className)); + } + + public function it_should_present_a_static_magic_method_as_string() + { + $className = __NAMESPACE__.'\\WithStaticMagicCall'; + $this->present([$className, 'undefinedMethod']) + ->shouldReturn(sprintf('%s::undefinedMethod()', $className)); + } + + public function it_should_present_an_invokable_object_as_string(WithMagicInvoke $object) + { + $className = get_class($object->getWrappedObject()); + $this->present($object)->shouldReturn(sprintf('[obj:%s]', $className)); + } +} + +class WithMethod +{ + public function specMethod() + { + } +} + +class WithStaticMethod +{ + public function specMethod() + { + } +} + +class WithMagicInvoke +{ + public function __invoke() + { + } +} + +class WithStaticMagicCall +{ + public static function __callStatic($method, $name) + { + } +} + +class WithMagicCall +{ + public function __call($method, $name) + { + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/ComposedValuePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/ComposedValuePresenterSpec.php new file mode 100644 index 0000000..a689dca --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/ComposedValuePresenterSpec.php @@ -0,0 +1,98 @@ +shouldHaveType('PhpSpec\Formatter\Presenter\Value\ComposedValuePresenter'); + } + + public function it_should_accept_a_type_presenter(TypePresenter $typePresenter) + { + $this->addTypePresenter($typePresenter)->shouldReturn(null); + } + + public function it_should_call_supports_on_value_presenters_until_one_returns_true( + TypePresenter $presenter1, + TypePresenter $presenter2, + TypePresenter $presenter3 + ) { + $value = 'blah'; + + $presenter1->getPriority()->willReturn(3); + $presenter2->getPriority()->willReturn(2); + $presenter3->getPriority()->willReturn(1); + + $this->addTypePresenter($presenter1); + $this->addTypePresenter($presenter2); + $this->addTypePresenter($presenter3); + + $presenter1->supports($value)->willReturn(false)->shouldBeCalled(); + $presenter2->supports($value)->willReturn(true)->shouldBeCalled(); + $presenter2->present(Argument::any())->shouldBeCalled(); + $presenter3->supports($value)->shouldNotBeCalled(); + + $this->presentValue($value); + } + + public function it_should_order_presenters_by_their_priority_in_descending_order( + TypePresenter $presenter1, + TypePresenter $presenter2 + ) { + $value = 'foo'; + + $presenter1->getPriority()->willReturn(10); + $presenter2->getPriority()->willReturn(20); + + $this->addTypePresenter($presenter1); + $this->addTypePresenter($presenter2); + + $presenter1->supports($value)->shouldBeCalled()->willReturn(true); + $presenter2->supports($value)->shouldBeCalled(); + $presenter1->present(Argument::any())->shouldBeCalled(); + + $this->presentValue($value); + } + + public function it_should_call_present_on_a_supporting_type_presenter(TypePresenter $typePresenter) + { + $value = 'blah'; + + $typePresenter->supports($value)->willReturn(true); + $typePresenter->present($value)->shouldBeCalled(); + + $this->addTypePresenter($typePresenter); + $this->presentValue($value); + } + + public function it_should_return_the_type_presenter_presented_value(TypePresenter $typePresenter) + { + $value = 'blah'; + $presented = $value.'presented'; + + $typePresenter->supports($value)->willReturn(true); + $typePresenter->present($value)->willReturn($presented); + + $this->addTypePresenter($typePresenter); + $this->presentValue($value)->shouldReturn($presented); + } + + public function it_returns_a_default_when_no_type_presenters_support_the_value() + { + $this->presentValue('blah')->shouldReturn('[string:blah]'); + } + + public function it_should_present_a_simple_type_as_typed_value() + { + $this->presentValue(42)->shouldReturn('[integer:42]'); + $this->presentValue(42.0)->shouldReturn('[double:42]'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/NullTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/NullTypePresenterSpec.php new file mode 100644 index 0000000..c75d8e1 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/NullTypePresenterSpec.php @@ -0,0 +1,25 @@ +shouldImplement('PhpSpec\Formatter\Presenter\Value\TypePresenter'); + } + + public function it_should_support_null_values() + { + $this->supports(null)->shouldReturn(true); + } + + public function it_should_present_null_as_a_string() + { + $this->present(null)->shouldReturn('null'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/ObjectTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/ObjectTypePresenterSpec.php new file mode 100644 index 0000000..a427860 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/ObjectTypePresenterSpec.php @@ -0,0 +1,25 @@ +shouldImplement('PhpSpec\Formatter\Presenter\Value\TypePresenter'); + } + + public function it_should_support_object_values() + { + $this->supports(new \stdClass())->shouldReturn(true); + } + + public function it_should_present_an_object_as_a_string() + { + $this->present(new \stdClass())->shouldReturn('[obj:stdClass]'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/QuotingStringTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/QuotingStringTypePresenterSpec.php new file mode 100644 index 0000000..926dc35 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/QuotingStringTypePresenterSpec.php @@ -0,0 +1,27 @@ +shouldImplement('PhpSpec\Formatter\Presenter\Value\StringTypePresenter'); + } + + public function it_should_support_string_values() + { + $this->supports('')->shouldReturn(true); + $this->supports('foo')->shouldReturn(true); + } + + public function it_should_present_a_string_as_a_quoted_string() + { + $this->present('')->shouldReturn('""'); + $this->present('foo')->shouldReturn('"foo"'); + } +} diff --git a/spec/PhpSpec/Formatter/Presenter/Value/TruncatingStringTypePresenterSpec.php b/spec/PhpSpec/Formatter/Presenter/Value/TruncatingStringTypePresenterSpec.php new file mode 100644 index 0000000..b8a5d82 --- /dev/null +++ b/spec/PhpSpec/Formatter/Presenter/Value/TruncatingStringTypePresenterSpec.php @@ -0,0 +1,48 @@ +beConstructedWith($stringTypePresenter); + } + + public function it_is_a_string_type_presenter() + { + $this->shouldImplement('PhpSpec\Formatter\Presenter\Value\StringTypePresenter'); + } + + public function it_should_support_string_values(StringTypePresenter $stringTypePresenter) + { + $stringTypePresenter->supports('foo')->willReturn(true); + $this->supports('foo')->shouldReturn(true); + } + + public function it_should_pass_short_values_directly_to_the_decorated_string_type_presenter( + StringTypePresenter $stringTypePresenter + ) { + $stringTypePresenter->present('foo')->willReturn('zfooz'); + $this->present('foo')->shouldReturn('zfooz'); + } + + public function it_should_return_long_values_truncated( + StringTypePresenter $stringTypePresenter + ) { + $stringTypePresenter->present('some_string_longer_than_t...') + ->willReturn('some_string_longer_than_t...'); + $this->present('some_string_longer_than_twenty_five_chars')->shouldReturn('some_string_longer_than_t...'); + } + + public function it_presents_only_first_line_of_multiline_string(StringTypePresenter $stringTypePresenter) + { + $stringTypePresenter->present('some...')->willReturn('some...'); + $this->present("some\nmultiline\nvalue")->shouldReturn('some...'); + } +} diff --git a/spec/PhpSpec/Formatter/ProgressFormatterSpec.php b/spec/PhpSpec/Formatter/ProgressFormatterSpec.php new file mode 100644 index 0000000..e90f362 --- /dev/null +++ b/spec/PhpSpec/Formatter/ProgressFormatterSpec.php @@ -0,0 +1,117 @@ +beConstructedWith($presenter, $io, $stats); + $io->getBlockWidth()->willReturn(80); + $io->isDecorated()->willReturn(false); + $io->writeTemp(Argument::cetera())->should(function () { + }); + } + + public function it_is_an_event_subscriber() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_outputs_progress_as_0_when_0_examples_have_run(ExampleEvent $event, ConsoleIO $io, StatisticsCollector $stats) + { + $stats->getEventsCount()->willReturn(0); + $stats->getCountsHash()->willReturn([ + 'passed' => 0, + 'pending' => 0, + 'skipped' => 0, + 'failed' => 0, + 'broken' => 0, + ]); + $stats->getTotalSpecs()->willReturn(0); + $stats->getTotalSpecsCount()->willReturn(0); + + $io->isDecorated()->willReturn(false); + $io->getBlockWidth()->willReturn(0); + + $expected = '/ skipped: 0% / pending: 0% / passed: 0% / failed: 0% / broken: 0% / 0 examples'; + $io->writeTemp($expected)->shouldBeCalled(); + + $this->afterExample($event); + } + + public function it_outputs_progress_as_0_when_0_examples_have_passed(ExampleEvent $event, ConsoleIO $io, StatisticsCollector $stats) + { + $stats->getEventsCount()->willReturn(1); + $stats->getCountsHash()->willReturn([ + 'passed' => 1, + 'pending' => 0, + 'skipped' => 0, + 'failed' => 0, + 'broken' => 0, + ]); + $stats->getTotalSpecs()->willReturn(1); + $stats->getTotalSpecsCount()->willReturn(1); + + $io->isDecorated()->willReturn(false); + $io->getBlockWidth()->willReturn(0); + + $expected = '/ skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% / 1 examples'; + $io->writeTemp($expected)->shouldBeCalled(); + + $this->afterExample($event); + } + + public function it_outputs_progress_as_100_when_1_of_3_examples_have_passed(ExampleEvent $event, ConsoleIO $io, StatisticsCollector $stats) + { + $stats->getEventsCount()->willReturn(1); + $stats->getCountsHash()->willReturn([ + 'passed' => 1, + 'pending' => 0, + 'skipped' => 0, + 'failed' => 0, + 'broken' => 0, + ]); + $stats->getTotalSpecs()->willReturn(1); + $stats->getTotalSpecsCount()->willReturn(3); + + $io->isDecorated()->willReturn(false); + $io->getBlockWidth()->willReturn(0); + + $expected = '/ skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% / 1 examples'; + $io->writeTemp($expected)->shouldBeCalled(); + + $this->afterExample($event); + } + + public function it_outputs_progress_as_33_when_3_of_3_examples_have_run_and_one_passed(ExampleEvent $event, ConsoleIO $io, StatisticsCollector $stats) + { + $stats->getEventsCount()->willReturn(3); + $stats->getCountsHash()->willReturn([ + 'passed' => 1, + 'pending' => 0, + 'skipped' => 0, + 'failed' => 2, + 'broken' => 0, + ]); + $stats->getTotalSpecs()->willReturn(3); + $stats->getTotalSpecsCount()->willReturn(3); + + $io->isDecorated()->willReturn(false); + $io->getBlockWidth()->willReturn(0); + + $expected = '/ skipped: 0% / pending: 0% / passed: 33% / failed: 66% / broken: 0% / 3 examples'; + $io->writeTemp($expected)->shouldBeCalled(); + + $this->afterExample($event); + } +} diff --git a/spec/PhpSpec/Formatter/TapFormatterSpec.php b/spec/PhpSpec/Formatter/TapFormatterSpec.php new file mode 100644 index 0000000..d6afc41 --- /dev/null +++ b/spec/PhpSpec/Formatter/TapFormatterSpec.php @@ -0,0 +1,149 @@ +beConstructedWith($presenter, $io, $stats); + } + + public function it_is_an_event_subscriber() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_outputs_version_on_beforesuite_event(SuiteEvent $event, ConsoleIO $io) + { + $this->beforeSuite($event); + $expected = 'TAP version 13'; + $io->writeln($expected)->shouldHaveBeenCalled(); + } + + public function it_outputs_plan_on_aftersuite_event(SuiteEvent $suiteEvent, ExampleEvent $exampleEvent, ExampleNode $example, ConsoleIO $io, StatisticsCollector $stats) + { + $stats->getEventsCount()->willReturn(3); + $exampleEvent->getExample()->willReturn($example); + $example->getTitle()->willReturn('foobar'); + $exampleEvent->getResult()->willReturn(0); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->writeln('1..3')->shouldHaveBeenCalled(); + } + + public function it_outputs_progress_on_afterexample_event(SpecificationEvent $specEvent, ExampleEvent $exampleEvent, ExampleNode $example, SpecificationNode $spec, ConsoleIO $io, StatisticsCollector $stats) + { + $specEvent->getSpecification()->willReturn($spec); + $exampleEvent->getExample()->willReturn($example); + $exampleEvent->getResult()->willReturn(ExampleEvent::PASSED); + + $example->getTitle()->willReturn('it foobar'); + $spec->getTitle()->willReturn('spec1'); + $this->beforeSpecification($specEvent); + $this->afterExample($exampleEvent); + + $example->getTitle()->willReturn('its foobar'); + $spec->getTitle()->willReturn('spec2'); + $this->beforeSpecification($specEvent); + $this->afterExample($exampleEvent); + + $expected1 = 'ok 1 - spec1: foobar'; + $expected2 = 'ok 2 - spec2: foobar'; + $io->writeln($expected1)->shouldHaveBeenCalled(); + $io->writeln($expected2)->shouldHaveBeenCalled(); + } + + public function it_outputs_failure_progress_on_afterexample_event(SpecificationEvent $specEvent, ExampleEvent $exampleEvent, ExampleNode $example, SpecificationNode $spec, ConsoleIO $io, StatisticsCollector $stats) + { + $specEvent->getSpecification()->willReturn($spec); + $exampleEvent->getExample()->willReturn($example); + $example->getTitle()->willReturn('foobar'); + $exampleEvent->getResult()->willReturn(ExampleEvent::FAILED); + $exampleEvent->getException()->willReturn(new \Exception('Something failed.')); + + $spec->getTitle()->willReturn('spec1'); + $this->beforeSpecification($specEvent); + $this->afterExample($exampleEvent); + + $expected = "not ok 1 - spec1: foobar\n ---\n message: 'Something failed.'\n severity: fail\n ..."; + $io->writeln($expected)->shouldHaveBeenCalled(); + } + + public function it_outputs_skip_progress_on_afterexample_event(SpecificationEvent $specEvent, ExampleEvent $exampleEvent, ExampleNode $example, SpecificationNode $spec, ConsoleIO $io, StatisticsCollector $stats) + { + $specEvent->getSpecification()->willReturn($spec); + $exampleEvent->getExample()->willReturn($example); + $example->getTitle()->willReturn('foobar'); + $exampleEvent->getResult()->willReturn(ExampleEvent::SKIPPED); + $exampleEvent->getException()->willReturn(new \Exception('no reason')); + + $spec->getTitle()->willReturn('spec1'); + $this->beforeSpecification($specEvent); + $this->afterExample($exampleEvent); + + $expected = 'ok 1 - spec1: foobar # SKIP no reason'; + $io->writeln($expected)->shouldHaveBeenCalled(); + } + + public function it_outputs_todo_progress_on_afterexample_event(SpecificationEvent $specEvent, ExampleEvent $exampleEvent, ExampleNode $example, SpecificationNode $spec, ConsoleIO $io, StatisticsCollector $stats) + { + $specEvent->getSpecification()->willReturn($spec); + $exampleEvent->getExample()->willReturn($example); + $example->getTitle()->willReturn('foobar'); + $exampleEvent->getResult()->willReturn(ExampleEvent::PENDING); + $exampleEvent->getException()->willReturn(new \Exception("no\nreason")); + + $spec->getTitle()->willReturn('spec1'); + $this->beforeSpecification($specEvent); + $this->afterExample($exampleEvent); + + $expected = "not ok 1 - spec1: foobar # TODO no / reason\n ---\n severity: todo\n ..."; + $io->writeln($expected)->shouldHaveBeenCalled(); + } + + public function it_outputs_broken_progress_on_afterexample_event(SpecificationEvent $specEvent, ExampleEvent $exampleEvent, ExampleNode $example, SpecificationNode $spec, ConsoleIO $io, StatisticsCollector $stats) + { + $specEvent->getSpecification()->willReturn($spec); + $exampleEvent->getExample()->willReturn($example); + $example->getTitle()->willReturn('foobar'); + $exampleEvent->getResult()->willReturn(ExampleEvent::BROKEN); + $exampleEvent->getException()->willReturn(new \Exception("Something broke's.\nIt hurts.")); + + $spec->getTitle()->willReturn('spec1'); + $this->beforeSpecification($specEvent); + $this->afterExample($exampleEvent); + + $expected = "not ok 1 - spec1: foobar\n ---\n message: \"Something broke's.\\nIt hurts.\"\n severity: fail\n ..."; + $io->writeln($expected)->shouldHaveBeenCalled(); + } + + public function it_outputs_undefined_progress_on_afterexample_event(SpecificationEvent $specEvent, ExampleEvent $exampleEvent, ExampleNode $example, SpecificationNode $spec, ConsoleIO $io, StatisticsCollector $stats) + { + $specEvent->getSpecification()->willReturn($spec); + $exampleEvent->getExample()->willReturn($example); + $example->getTitle()->willReturn('foobar'); + $exampleEvent->getResult()->willReturn(999); + + $spec->getTitle()->willReturn('spec1'); + $this->beforeSpecification($specEvent); + $this->afterExample($exampleEvent); + + $expected = "not ok 1 - spec1: foobar\n ---\n message: 'The example result type was unknown to formatter'\n severity: fail\n ..."; + $io->writeln($expected)->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Listener/ClassNotFoundListenerSpec.php b/spec/PhpSpec/Listener/ClassNotFoundListenerSpec.php new file mode 100644 index 0000000..be1e545 --- /dev/null +++ b/spec/PhpSpec/Listener/ClassNotFoundListenerSpec.php @@ -0,0 +1,94 @@ +writeln(Argument::any())->should(function () { + }); + $io->askConfirmation(Argument::any())->willReturn(false); + + $exception->getClassname()->willReturn('SomeClass'); + + $resourceManager->createResource(Argument::cetera())->willReturn($resource); + + $this->beConstructedWith($io, $resourceManager, $generatorManager); + } + + public function it_does_not_prompt_for_class_generation_if_no_exception_was_thrown($exampleEvent, $suiteEvent, $io) + { + $io->isCodeGenerationEnabled()->willReturn(true); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } + + public function it_does_not_prompt_for_class_generation_if_non_class_exception_was_thrown($exampleEvent, $suiteEvent, $io, \InvalidArgumentException $invalidArgumentException) + { + $exampleEvent->getException()->willReturn($invalidArgumentException); + $io->isCodeGenerationEnabled()->willReturn(true); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } + + public function it_prompts_for_class_generation_if_prophecy_classnotfoundexception_was_thrown_and_input_is_interactive($exampleEvent, $suiteEvent, $io, ProphecyClassException $prophecyException) + { + $exampleEvent->getException()->willReturn($prophecyException); + $io->isCodeGenerationEnabled()->willReturn(true); + + $io->askConfirmation(Argument::any())->shouldBeCalled()->willReturn(false); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + } + + public function it_prompts_for_method_generation_if_phpspec_classnotfoundexception_was_thrown_and_input_is_interactive($exampleEvent, $suiteEvent, $io, PhpSpecClassException $exception) + { + $exampleEvent->getException()->willReturn($exception); + $io->isCodeGenerationEnabled()->willReturn(true); + + $io->askConfirmation(Argument::any())->shouldBeCalled()->willReturn(false); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + } + + public function it_does_not_prompt_for_class_generation_if_input_is_not_interactive($exampleEvent, $suiteEvent, $io, PhpSpecClassException $exception) + { + $exampleEvent->getException()->willReturn($exception); + $io->isCodeGenerationEnabled()->willReturn(false); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } +} diff --git a/spec/PhpSpec/Listener/CollaboratorMethodNotFoundListenerSpec.php b/spec/PhpSpec/Listener/CollaboratorMethodNotFoundListenerSpec.php new file mode 100644 index 0000000..e544225 --- /dev/null +++ b/spec/PhpSpec/Listener/CollaboratorMethodNotFoundListenerSpec.php @@ -0,0 +1,231 @@ +beConstructedWith($io, $resources, $generator, $nameChecker); + $event->getException()->willReturn($exception); + + $io->isCodeGenerationEnabled()->willReturn(true); + $io->askConfirmation(Argument::any())->willReturn(false); + + $resources->createResource(Argument::any())->willReturn($resource); + + $exception->getArguments()->willReturn([]); + $nameChecker->isNameValid('aMethod')->willReturn(true); + } + + public function it_is_an_event_subscriber() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_listens_to_afterexample_events() + { + $this->getSubscribedEvents()->shouldReturn([ + 'afterExample' => ['afterExample', 10], + 'afterSuite' => ['afterSuite', -10] + ]); + } + + public function it_does_not_prompt_when_no_exception_is_thrown(ConsoleIO $io, ExampleEvent $event, SuiteEvent $suiteEvent) + { + $event->getException()->willReturn(null); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_prompts_the_user_when_a_prophecy_method_exception_is_thrown( + ConsoleIO $io, + ExampleEvent $event, + SuiteEvent $suiteEvent, + MethodNotFoundException $exception + ) { + $exception->getClassname()->willReturn('spec\PhpSpec\Listener\DoubleOfInterface'); + $exception->getMethodName()->willReturn('aMethod'); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldHaveBeenCalled(); + } + + public function it_does_not_prompt_when_wrong_exception_is_thrown(ConsoleIO $io, ExampleEvent $event, SuiteEvent $suiteEvent) + { + $event->getException()->willReturn(new \RuntimeException()); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_collaborator_is_not_an_interface( + ConsoleIO $io, + ExampleEvent $event, + SuiteEvent $suiteEvent, + MethodNotFoundException $exception + ) { + $exception->getClassname()->willReturn('spec\PhpSpec\Listener\DoubleOfStdClass'); + $exception->getMethodName()->willReturn('aMethod'); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_code_generation_is_disabled( + ConsoleIO $io, + ExampleEvent $event, + SuiteEvent $suiteEvent, + MethodNotFoundException $exception + ) { + $io->isCodeGenerationEnabled()->willReturn(false); + + $exception->getClassname()->willReturn('spec\PhpSpec\Listener\DoubleOfInterface'); + $exception->getMethodName()->willReturn('aMethod'); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_if_it_cannot_generate_the_resource( + ConsoleIO $io, + ResourceManager $resources, + ExampleEvent $event, + SuiteEvent $suiteEvent, + MethodNotFoundException $exception + ) { + $resources->createResource(Argument::any())->willThrow(new ResourceCreationException()); + + $exception->getClassname()->willReturn('spec\PhpSpec\Listener\DoubleOfInterface'); + $exception->getMethodName()->willReturn('aMethod'); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_generates_the_method_signature_when_user_says_yes_at_prompt( + ConsoleIO $io, + ExampleEvent $event, + SuiteEvent $suiteEvent, + MethodNotFoundException $exception, + Resource $resource, + GeneratorManager $generator + ) { + $io->askConfirmation(Argument::any())->willReturn(true); + + $exception->getClassname()->willReturn('spec\PhpSpec\Listener\DoubleOfInterface'); + $exception->getMethodName()->willReturn('aMethod'); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $generator->generate($resource, 'method-signature', Argument::any())->shouldHaveBeenCalled(); + } + + public function it_marks_the_suite_as_being_worth_rerunning_when_generation_happens( + ConsoleIO $io, + ExampleEvent $event, + SuiteEvent $suiteEvent, + MethodNotFoundException $exception + ) { + $io->askConfirmation(Argument::any())->willReturn(true); + + $exception->getClassname()->willReturn('spec\PhpSpec\Listener\DoubleOfInterface'); + $exception->getMethodName()->willReturn('aMethod'); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + + $suiteEvent->markAsWorthRerunning()->shouldHaveBeenCalled(); + } + + public function it_warns_if_a_method_name_is_wrong( + ExampleEvent $event, + SuiteEvent $suiteEvent, + ConsoleIO $io, + NameChecker $nameChecker + ) { + $exception = new MethodNotFoundException('Error', new DoubleOfInterface(), 'throw'); + + $event->getException()->willReturn($exception); + $nameChecker->isNameValid('throw')->willReturn(false); + + $io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled(); + $io->askConfirmation(Argument::any())->shouldNotBeCalled(); + + $this->afterExample($event); + $this->afterSuite($suiteEvent); + } + + public function it_prompts_and_warns_when_one_method_name_is_correct_but_other_reserved( + ExampleEvent $event, + SuiteEvent $suiteEvent, + ConsoleIO $io, + NameChecker $nameChecker + ) { + $this->callAfterExample($event, $nameChecker, 'throw', false); + $this->callAfterExample($event, $nameChecker, 'foo'); + + $io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled(); + $io->askConfirmation(Argument::any())->shouldBeCalled(); + $suiteEvent->markAsNotWorthRerunning()->shouldBeCalled(); + + $this->afterSuite($suiteEvent); + } + + private function callAfterExample($event, $nameChecker, $method, $isNameValid = true) + { + $exception = new MethodNotFoundException('Error', DoubleOfInterface::class, $method); + $event->getException()->willReturn($exception); + $nameChecker->isNameValid($method)->willReturn($isNameValid); + + $this->afterExample($event); + } +} + +interface ExampleInterface +{ +} + +class DoubleOfInterface extends \stdClass implements ExampleInterface, DoubleInterface +{ +} + +class DoubleOfStdClass extends \stdClass implements DoubleInterface +{ +} diff --git a/spec/PhpSpec/Listener/CollaboratorNotFoundListenerSpec.php b/spec/PhpSpec/Listener/CollaboratorNotFoundListenerSpec.php new file mode 100644 index 0000000..2a454f3 --- /dev/null +++ b/spec/PhpSpec/Listener/CollaboratorNotFoundListenerSpec.php @@ -0,0 +1,150 @@ +beConstructedWith($io, $resources, $generator); + + $resources->createResource(Argument::any())->willReturn($resource); + $resource->getSpecNamespace()->willReturn('spec'); + + $exampleEvent->getException()->willReturn($exception); + $exception->getCollaboratorName()->willReturn('Example\ExampleClass'); + + $io->isCodeGenerationEnabled()->willReturn(true); + $io->askConfirmation(Argument::any())->willReturn(false); + $io->writeln(Argument::any())->should(function () { + }); + } + + public function it_listens_to_afterexample_and_aftersuite_events() + { + $this->getSubscribedEvents()->shouldReturn([ + 'afterExample' => ['afterExample', 10], + 'afterSuite' => ['afterSuite', -10] + ]); + } + + public function it_prompts_to_generate_missing_collaborator( + ConsoleIO $io, + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent + ) { + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation( + 'Would you like me to generate an interface `Example\ExampleClass` for you?' + )->shouldHaveBeenCalled(); + } + + public function it_does_not_prompt_to_generate_when_there_was_no_exception( + ConsoleIO $io, + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent + ) { + $exampleEvent->getException()->willReturn(null); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_to_generate_when_there_was_an_exception_of_the_wrong_type( + ConsoleIO $io, + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent, + \InvalidArgumentException $otherException + ) { + $exampleEvent->getException()->willReturn($otherException); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_code_generation_is_disabled( + ConsoleIO $io, + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent + ) { + $io->isCodeGenerationEnabled()->willReturn(false); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_collaborator_is_in_spec_namespace( + ConsoleIO $io, + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent, + CollaboratorNotFoundException $exception + ) { + $exception->getCollaboratorName()->willReturn('spec\Example\ExampleClass'); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_generates_interface_when_prompt_is_answered_with_yes( + ConsoleIO $io, + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent, + GeneratorManager $generator, + Resource $resource + ) { + $io->askConfirmation( + 'Would you like me to generate an interface `Example\ExampleClass` for you?' + )->willReturn(true); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $generator->generate($resource, 'interface')->shouldHaveBeenCalled(); + $suiteEvent->markAsWorthRerunning()->shouldHaveBeenCalled(); + } + + public function it_does_not_generate_interface_when_prompt_is_answered_with_no( + ConsoleIO $io, + ExampleEvent $exampleEvent, + SuiteEvent $suiteEvent, + GeneratorManager $generator + ) { + $io->askConfirmation( + 'Would you like me to generate an interface `Example\ExampleClass` for you?' + )->willReturn(false); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $generator->generate(Argument::cetera())->shouldNotHaveBeenCalled(); + $suiteEvent->markAsWorthRerunning()->shouldNotHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Listener/CurrentExampleListenerSpec.php b/spec/PhpSpec/Listener/CurrentExampleListenerSpec.php new file mode 100644 index 0000000..b6cec7d --- /dev/null +++ b/spec/PhpSpec/Listener/CurrentExampleListenerSpec.php @@ -0,0 +1,58 @@ +beConstructedWith($currentExample); + } + + public function it_is_initializable() + { + $this->shouldHaveType('PhpSpec\Listener\CurrentExampleListener'); + } + + public function it_should_implement_event_subscriber_interface() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_should_call_beforeCurrentExample(ExampleEvent $example) + { + $currentExample = new CurrentExampleTracker(); + $fatalError = 'Fatal error happened before example'; + $example->getTitle()->willReturn($fatalError); + $currentExample->setCurrentExample($fatalError); + $this->beforeCurrentExample($example); + $example->getTitle()->shouldHaveBeenCalled(); + } + + public function it_should_call_afterCurrentExample(ExampleEvent $example) + { + $currentExample = new CurrentExampleTracker(); + $currentExample->setCurrentExample(null); + $example->getTitle()->willReturn(null); + $this->afterCurrentExample(); + $example->getTitle()->shouldNotHaveBeenCalled(); + } + + public function it_should_call_afterSuiteEvent(SuiteEvent $example) + { + $fatalError = '3'; + $currentExample = new CurrentExampleTracker(); + $currentExample->setCurrentExample('Exited with code: '.$fatalError); + $example->getResult()->willReturn($fatalError); + $this->afterSuiteEvent($example); + $example->getResult()->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Listener/MethodNotFoundListenerSpec.php b/spec/PhpSpec/Listener/MethodNotFoundListenerSpec.php new file mode 100644 index 0000000..19d63de --- /dev/null +++ b/spec/PhpSpec/Listener/MethodNotFoundListenerSpec.php @@ -0,0 +1,129 @@ +writeln(Argument::any())->should(function () { + }); + $io->askConfirmation(Argument::any())->willReturn(false); + + $this->beConstructedWith($io, $resourceManager, $generatorManager, $nameChecker); + $io->isCodeGenerationEnabled()->willReturn(true); + + $nameChecker->isNameValid(Argument::any())->willReturn(false); + + $resourceManager->createResource(Argument::cetera())->willReturn($resource); + } + + public function it_does_not_prompt_for_method_generation_if_no_exception_was_thrown($exampleEvent, $suiteEvent, $io) + { + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } + + public function it_does_not_prompt_for_method_generation_if_non_methodnotfoundexception_was_thrown($exampleEvent, $suiteEvent, $io, \InvalidArgumentException $exception) + { + $exampleEvent->getException()->willReturn($exception); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } + + public function it_prompts_for_method_generation_if_methodnotfoundexception_was_thrown_and_input_is_interactive( + $exampleEvent, + $suiteEvent, + $io, + NameChecker $nameChecker + ) { + $exception = new MethodNotFoundException('Error', new \stdClass(), 'bar'); + + $exampleEvent->getException()->willReturn($exception); + $nameChecker->isNameValid('bar')->willReturn(true); + + $io->askConfirmation(Argument::any())->shouldBeCalled()->willReturn(false); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + } + + public function it_does_not_prompt_for_method_generation_if_input_is_not_interactive($exampleEvent, $suiteEvent, $io, MethodNotFoundException $exception) + { + $exampleEvent->getException()->willReturn($exception); + $exception->getMethodName()->willReturn('someMethod'); + $exception->getSubject()->willReturn(new \stdClass); + $exception->getArguments()->willReturn([]); + + $io->isCodeGenerationEnabled()->willReturn(false); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } + + public function it_warns_when_method_name_is_reserved( + $exampleEvent, + $suiteEvent, + ConsoleIO $io, + NameChecker $nameChecker + ) { + $this->callAfterExample($exampleEvent, $nameChecker, 'throw', false); + + $io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled(); + + $this->afterSuite($suiteEvent); + } + + public function it_prompts_and_warns_when_one_method_name_is_correct_but_other_reserved( + $exampleEvent, + SuiteEvent $suiteEvent, + ConsoleIO $io, + NameChecker $nameChecker + ) { + $this->callAfterExample($exampleEvent, $nameChecker, 'throw', false); + $this->callAfterExample($exampleEvent, $nameChecker, 'foo'); + + $io->writeBrokenCodeBlock("I cannot generate the method 'throw' for you because it is a reserved keyword", 2)->shouldBeCalled(); + $io->askConfirmation('Do you want me to create `stdClass::foo()` for you?')->shouldBeCalled(); + $suiteEvent->markAsNotWorthRerunning()->shouldBeCalled(); + + $this->afterSuite($suiteEvent); + } + + private function callAfterExample($exampleEvent, $nameChecker, $method, $isNameValid = true) + { + $exception = new MethodNotFoundException('Error', new \stdClass(), $method); + $exampleEvent->getException()->willReturn($exception); + $nameChecker->isNameValid($method)->willReturn($isNameValid); + + $this->afterExample($exampleEvent); + } +} diff --git a/spec/PhpSpec/Listener/MethodReturnedNullListenerSpec.php b/spec/PhpSpec/Listener/MethodReturnedNullListenerSpec.php new file mode 100644 index 0000000..6259cdb --- /dev/null +++ b/spec/PhpSpec/Listener/MethodReturnedNullListenerSpec.php @@ -0,0 +1,272 @@ +beConstructedWith($io, $resourceManager, $generatorManager, $methodAnalyser); + + $exampleEvent->getException()->willReturn($notEqualException); + $notEqualException->getActual()->willReturn(null); + $notEqualException->getExpected()->willReturn(100); + + $methodCallEvent->getMethod()->willReturn('foo'); + $methodCallEvent->getSubject()->willReturn(new \stdClass); + + $io->isCodeGenerationEnabled()->willReturn(true); + + $io->askConfirmation(Argument::any())->willReturn(false); + + $io->isFakingEnabled()->willReturn(true); + + $methodAnalyser->methodIsEmpty(Argument::cetera())->willReturn(true); + $methodAnalyser->getMethodOwnerName(Argument::cetera())->willReturn('Foo'); + + $resourceManager->createResource(Argument::cetera())->willReturn($resource); + } + + public function it_is_an_event_listener() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_listens_to_examples_to_spot_failures() + { + $this->getSubscribedEvents()->shouldHaveKey('afterExample'); + } + + public function it_listens_to_suites_to_know_when_to_prompt() + { + $this->getSubscribedEvents()->shouldHaveKey('afterSuite'); + } + + public function it_listens_to_method_calls_to_see_what_has_failed() + { + $this->getSubscribedEvents()->shouldHaveKey('afterMethodCall'); + } + + public function it_does_not_prompt_when_wrong_type_of_exception_is_thrown( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + ConsoleIO $io, + SuiteEvent $event + ) { + $exampleEvent->getException()->willReturn(new \Exception()); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_actual_value_is_not_null( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + NotEqualException $notEqualException, + ConsoleIO $io, + SuiteEvent $event + ) { + $exampleEvent->getException()->willReturn($notEqualException); + $notEqualException->getActual()->willReturn(90); + $notEqualException->getExpected()->willReturn(100); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_expected_value_is_an_object( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + NotEqualException $notEqualException, + ConsoleIO $io, + SuiteEvent $event + ) { + $exampleEvent->getException()->willReturn($notEqualException); + $notEqualException->getActual()->willReturn(null); + $notEqualException->getExpected()->willReturn(new \DateTime()); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_if_no_method_was_called_beforehand(ExampleEvent $exampleEvent, ConsoleIO $io, SuiteEvent $event) + { + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_there_is_a_problem_creating_the_resource( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + ConsoleIO $io, + ResourceManager $resourceManager, + SuiteEvent $event + ) { + $resourceManager->createResource(Argument::any())->willThrow(new \RuntimeException()); + $methodCallEvent->getSubject()->willReturn(new \stdClass()); + $methodCallEvent->getMethod()->willReturn(''); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_input_is_not_interactive( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + ConsoleIO $io, + SuiteEvent $event + ) { + $io->isCodeGenerationEnabled()->willReturn(false); + $methodCallEvent->getSubject()->willReturn(new \stdClass()); + $methodCallEvent->getMethod()->willReturn(''); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_method_is_not_empty( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + ConsoleIO $io, + MethodAnalyser $methodAnalyser, + SuiteEvent $event + ) { + $methodCallEvent->getMethod()->willReturn('myMethod'); + $methodCallEvent->getSubject()->willReturn(new \DateTime()); + + $methodAnalyser->methodIsEmpty('DateTime', 'myMethod')->willReturn(false); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_multiple_contradictory_examples_are_found( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + NotEqualException $notEqualException, + ConsoleIO $io, + ExampleEvent $exampleEvent2, + NotEqualException $notEqualException2, + SuiteEvent $event + ) { + $exampleEvent->getException()->willReturn($notEqualException); + $exampleEvent2->getException()->willReturn($notEqualException2); + + $notEqualException->getActual()->willReturn(null); + $notEqualException2->getActual()->willReturn(null); + + $notEqualException->getExpected()->willReturn('foo'); + $notEqualException2->getExpected()->willReturn('bar'); + + $methodCallEvent->getSubject()->willReturn(new \stdClass()); + $methodCallEvent->getMethod()->willReturn(''); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent2); + + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_does_not_prompt_when_io_has_faking_disabled( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + ConsoleIO $io, + SuiteEvent $event + ) { + $io->isFakingEnabled()->willReturn(false); + $methodCallEvent->getSubject()->willReturn(new \stdClass()); + $methodCallEvent->getMethod()->willReturn(''); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function it_prompts_when_correct_type_of_exception_is_thrown( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + ConsoleIO $io, + SuiteEvent $event + ) { + $methodCallEvent->getSubject()->willReturn(new \stdClass()); + $methodCallEvent->getMethod()->willReturn(''); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $io->askConfirmation(Argument::any())->shouldHaveBeenCalled(); + } + + public function it_invokes_method_body_generation_when_prompt_is_answered_yes( + MethodCallEvent $methodCallEvent, + ExampleEvent $exampleEvent, + ConsoleIO $io, + GeneratorManager $generatorManager, + ResourceManager $resourceManager, + Resource $resource, + SuiteEvent $event + ) { + $io->askConfirmation(Argument::any())->willReturn(true); + $resourceManager->createResource(Argument::any())->willReturn($resource); + + $methodCallEvent->getSubject()->willReturn(new \stdClass()); + $methodCallEvent->getMethod()->willReturn('myMethod'); + + $this->afterMethodCall($methodCallEvent); + $this->afterExample($exampleEvent); + $this->afterSuite($event); + + $generatorManager->generate($resource, 'returnConstant', ['method' => 'myMethod', 'expected' => 100]) + ->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Listener/NamedConstructorNotFoundListenerSpec.php b/spec/PhpSpec/Listener/NamedConstructorNotFoundListenerSpec.php new file mode 100644 index 0000000..4f9324d --- /dev/null +++ b/spec/PhpSpec/Listener/NamedConstructorNotFoundListenerSpec.php @@ -0,0 +1,93 @@ +writeln(Argument::any())->should(function () { + }); + ; + $io->askConfirmation(Argument::any())->willReturn(false); + + $this->beConstructedWith($io, $resourceManager, $generatorManager); + + $exception->getMethodName()->willReturn('someMethod'); + $exception->getSubject()->willReturn(new \stdClass()); + $exception->getArguments()->willReturn([]); + + $resourceManager->createResource(Argument::cetera())->willReturn($resource); + } + + public function it_does_not_prompt_for_method_generation_if_no_exception_was_thrown($exampleEvent, $suiteEvent, $io) + { + $io->isCodeGenerationEnabled()->willReturn(true); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } + + public function it_does_not_prompt_for_method_generation_if_non_namedconstructornotfoundexception_was_thrown($exampleEvent, $suiteEvent, $io, \InvalidArgumentException $invalidArgumentException) + { + $exampleEvent->getException()->willReturn($invalidArgumentException); + $io->isCodeGenerationEnabled()->willReturn(true); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } + + public function it_prompts_for_method_generation_if_namedconstructornotfoundexception_was_thrown_and_input_is_interactive($exampleEvent, $suiteEvent, $io, NamedConstructorNotFoundException $exception) + { + $exampleEvent->getException()->willReturn($exception); + $io->isCodeGenerationEnabled()->willReturn(true); + $io->askConfirmation(Argument::any())->willReturn(false); + + $exception->getSubject()->willReturn(new \stdClass()); + $exception->getMethodName()->willReturn(''); + $exception->getArguments()->willReturn([]); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldHaveBeenCalled(); + } + + public function it_does_not_prompt_for_method_generation_if_input_is_not_interactive($exampleEvent, $suiteEvent, $io, NamedConstructorNotFoundException $exception) + { + $exampleEvent->getException()->willReturn($exception); + $io->isCodeGenerationEnabled()->willReturn(false); + + $exception->getSubject()->willReturn(new \stdClass()); + $exception->getMethodName()->willReturn(''); + $exception->getArguments()->willReturn([]); + + $this->afterExample($exampleEvent); + $this->afterSuite($suiteEvent); + + $io->askConfirmation(Argument::any())->shouldNotBeenCalled(); + } +} diff --git a/spec/PhpSpec/Listener/RerunListenerSpec.php b/spec/PhpSpec/Listener/RerunListenerSpec.php new file mode 100644 index 0000000..85c05a1 --- /dev/null +++ b/spec/PhpSpec/Listener/RerunListenerSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($reRunner, $suitePrerequisites); + } + + public function it_subscribes_to_aftersuite() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + $this->getSubscribedEvents()->shouldHaveKey('afterSuite'); + } + + public function it_does_not_tell_the_rerunner_to_rerun_if_it_is_not_worth_doing_so(SuiteEvent $suiteEvent, ReRunner $reRunner) + { + $suiteEvent->isWorthRerunning()->willReturn(false); + + $this->afterSuite($suiteEvent); + + $reRunner->reRunSuite()->shouldNotHaveBeenCalled(); + } + + public function it_tells_the_rerunner_to_rerun_if_it_is_worth_doing_so(SuiteEvent $suiteEvent, ReRunner $reRunner) + { + $suiteEvent->isWorthRerunning()->willReturn(true); + + $this->afterSuite($suiteEvent); + + $reRunner->reRunSuite()->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Listener/StatisticsCollectorSpec.php b/spec/PhpSpec/Listener/StatisticsCollectorSpec.php new file mode 100644 index 0000000..f874aab --- /dev/null +++ b/spec/PhpSpec/Listener/StatisticsCollectorSpec.php @@ -0,0 +1,114 @@ +getResult()->willReturn(ExampleEvent::FAILED); + $passingExample->getResult()->willReturn(ExampleEvent::PASSED); + } + + public function it_is_an_event_listener() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_listens_to_stats_generating_events() + { + $subscribedEvents = $this->getSubscribedEvents(); + + $subscribedEvents->shouldHaveKey('afterExample'); + $subscribedEvents->shouldHaveKey('afterSpecification'); + $subscribedEvents->shouldHaveKey('beforeSuite'); + } + + public function it_knows_no_specs_have_run_initially() + { + $this->getTotalSpecs()->shouldReturn(0); + } + + public function it_counts_how_many_specs_have_run(SpecificationEvent $specEvent1, SpecificationEvent $specEvent2) + { + $this->afterSpecification($specEvent1); + $this->afterSpecification($specEvent2); + + $this->getTotalSpecs()->shouldReturn(2); + } + + public function it_knows_no_examples_have_run_initially() + { + $this->getEventsCount()->shouldReturn(0); + } + + public function it_counts_how_many_examples_have_run(ExampleEvent $failingExample, ExampleEvent $passingExample) + { + $this->afterExample($passingExample); + $this->afterExample($failingExample); + + $this->getEventsCount()->shouldReturn(2); + } + + public function it_logs_all_example_events(ExampleEvent $failingExample, ExampleEvent $passingExample) + { + $this->afterExample($passingExample); + $this->afterExample($failingExample); + + $this->getAllEvents()->shouldReturn([ + $passingExample, + $failingExample + ]); + } + + public function it_logs_all_example_events_by_type(ExampleEvent $failingExample, ExampleEvent $passingExample) + { + $this->afterExample($passingExample); + $this->afterExample($failingExample); + + $this->getPassedEvents()->shouldReturn([$passingExample]); + } + + public function it_counts_example_results_by_type(ExampleEvent $failingExample, ExampleEvent $passingExample) + { + $this->afterExample($passingExample); + $this->afterExample($failingExample); + + $this->getCountsHash()->shouldReturn( + [ + 'passed' => 1, + 'pending' => 0, + 'skipped' => 0, + 'failed' => 1, + 'broken' => 0, + ] + ); + } + + public function it_returns_the_worst_result_as_the_global_result(ExampleEvent $failingExample, ExampleEvent $passingExample) + { + $this->afterExample($passingExample); + $this->afterExample($failingExample); + + $this->getGlobalResult()->shouldReturn(ExampleEvent::FAILED); + } + + public function it_records_how_many_specs_are_in_the_suite(SuiteEvent $suiteEvent, Suite $suite, SpecificationNode $spec) + { + $suiteEvent->getSuite()->willReturn($suite); + $suite->getSpecifications()->willReturn([$spec]); + + $this->beforeSuite($suiteEvent); + + $this->getTotalSpecsCount()->shouldReturn(1); + } +} diff --git a/spec/PhpSpec/Listener/StopOnFailureListenerSpec.php b/spec/PhpSpec/Listener/StopOnFailureListenerSpec.php new file mode 100644 index 0000000..5f3804b --- /dev/null +++ b/spec/PhpSpec/Listener/StopOnFailureListenerSpec.php @@ -0,0 +1,68 @@ +isStopOnFailureEnabled()->willReturn(false); + $this->beConstructedWith($io); + } + + public function it_is_an_event_subscriber() + { + $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + } + + public function it_does_not_throw_any_exception_when_example_succeeds(ExampleEvent $event) + { + $event->getResult()->willReturn(ExampleEvent::PASSED); + + $this->afterExample($event); + } + + public function it_does_not_throw_any_exception_for_unimplemented_examples(ExampleEvent $event) + { + $event->getResult()->willReturn(ExampleEvent::PENDING); + + $this->afterExample($event); + } + + public function it_throws_an_exception_when_an_example_fails_and_option_is_set(ExampleEvent $event, $io) + { + $io->isStopOnFailureEnabled()->willReturn(true); + $event->getResult()->willReturn(ExampleEvent::FAILED); + + $this->shouldThrow('\PhpSpec\Exception\Example\StopOnFailureException')->duringAfterExample($event); + } + + public function it_does_not_throw_an_exception_when_an_example_fails_and_option_is_not_set(ExampleEvent $event) + { + $event->getResult()->willReturn(ExampleEvent::FAILED); + + $this->afterExample($event); + } + + public function it_throws_an_exception_when_an_example_breaks_and_option_is_set(ExampleEvent $event, $io) + { + $io->isStopOnFailureEnabled()->willReturn(true); + $event->getResult()->willReturn(ExampleEvent::BROKEN); + + $this->shouldThrow('\PhpSpec\Exception\Example\StopOnFailureException')->duringAfterExample($event); + } + + public function it_does_not_throw_an_exception_when_an_example_breaks_and_option_is_not_set(ExampleEvent $event) + { + $event->getResult()->willReturn(ExampleEvent::BROKEN); + + $this->afterExample($event); + } +} diff --git a/spec/PhpSpec/Loader/Node/ExampleNodeSpec.php b/spec/PhpSpec/Loader/Node/ExampleNodeSpec.php new file mode 100644 index 0000000..717fc7a --- /dev/null +++ b/spec/PhpSpec/Loader/Node/ExampleNodeSpec.php @@ -0,0 +1,81 @@ +isClosure()->willReturn(false); + $this->beConstructedWith('example node', $function); + } + + public function it_provides_a_link_to_title() + { + $this->getTitle()->shouldReturn('example node'); + } + + public function it_can_set_a_title() + { + $this->setTitle('node example'); + $this->getTitle()->shouldReturn('node example'); + $this->setTitle('example node'); + } + + public function it_provides_a_link_to_function($function) + { + $this->getFunctionReflection()->shouldReturn($function); + } + + public function it_provides_a_link_to_specification(SpecificationNode $specification) + { + $this->setSpecification($specification); + $this->getSpecification()->shouldReturn($specification); + } + + public function it_is_not_pending_by_default() + { + $this->isPending()->shouldReturn(false); + } + + public function it_is_pending_after_marked_as_pending_with_no_args() + { + $this->markAsPending(); + $this->isPending()->shouldReturn(true); + } + + public function it_is_pending_after_marked_as_pending_with_true() + { + $this->markAsPending(true); + $this->isPending()->shouldReturn(true); + } + + public function it_is_not_pending_after_marked_as_pending_with_false() + { + $this->markAsPending(false); + $this->isPending()->shouldReturn(false); + } + + public function it_returns_its_line_number(\ReflectionFunctionAbstract $function) + { + $function->getStartLine()->willReturn(100); + + $this->getLineNumber()->shouldReturn(100); + } + + public function it_returns_its_line_number_as_zero_if_constructed_with_closure( + \ReflectionFunctionAbstract $function + ) { + $function->isClosure()->willReturn(true); + + $this->getLineNumber()->shouldReturn(0); + } +} diff --git a/spec/PhpSpec/Loader/Node/SpecificationNodeSpec.php b/spec/PhpSpec/Loader/Node/SpecificationNodeSpec.php new file mode 100644 index 0000000..065896b --- /dev/null +++ b/spec/PhpSpec/Loader/Node/SpecificationNodeSpec.php @@ -0,0 +1,65 @@ +beConstructedWith('specification node', $class, $resource); + } + + public function it_is_countable() + { + $this->shouldImplement('Countable'); + } + + public function it_provides_a_link_to_title() + { + $this->getTitle()->shouldReturn('specification node'); + } + + public function it_provides_a_link_to_class($class) + { + $this->getClassReflection()->shouldReturn($class); + } + + public function it_provides_a_link_to_resource($resource) + { + $this->getResource()->shouldReturn($resource); + } + + public function it_provides_a_link_to_suite(Suite $suite) + { + $this->setSuite($suite); + $this->getSuite()->shouldReturn($suite); + } + + public function it_provides_a_link_to_examples(ExampleNode $example) + { + $this->addExample($example); + $this->addExample($example); + $this->addExample($example); + + $this->getExamples()->shouldReturn([$example, $example, $example]); + } + + public function it_provides_a_count_of_examples(ExampleNode $example) + { + $this->addExample($example); + $this->addExample($example); + $this->addExample($example); + + $this->count()->shouldReturn(3); + } +} diff --git a/spec/PhpSpec/Loader/SuiteSpec.php b/spec/PhpSpec/Loader/SuiteSpec.php new file mode 100644 index 0000000..ec09a38 --- /dev/null +++ b/spec/PhpSpec/Loader/SuiteSpec.php @@ -0,0 +1,38 @@ +shouldImplement('Countable'); + } + + public function it_provides_a_link_to_specifications(SpecificationNode $spec) + { + $this->addSpecification($spec); + $this->addSpecification($spec); + $this->addSpecification($spec); + + $this->getSpecifications()->shouldReturn([$spec, $spec, $spec]); + } + + public function it_provides_a_count_of_examples(SpecificationNode $spec) + { + $this->addSpecification($spec); + $this->addSpecification($spec); + $this->addSpecification($spec); + + $spec->count(Argument::any())->willReturn(5); + + $this->count()->shouldReturn(15); + } +} diff --git a/spec/PhpSpec/Loader/Transformer/InMemoryTypeHintIndexSpec.php b/spec/PhpSpec/Loader/Transformer/InMemoryTypeHintIndexSpec.php new file mode 100644 index 0000000..e16d1e5 --- /dev/null +++ b/spec/PhpSpec/Loader/Transformer/InMemoryTypeHintIndexSpec.php @@ -0,0 +1,44 @@ +shouldHaveType('PhpSpec\Loader\Transformer\TypeHintIndex'); + } + + public function it_is_case_insensitive() + { + $this->add('Foo', 'boz', '$bar', 'Baz'); + + $this->lookup('FoO', 'bOz', '$bAr')->shouldReturn('Baz'); + } + + public function it_remembers_the_typehints_that_are_added() + { + $this->add('Foo', 'boz', '$bar', 'Baz'); + + $this->lookup('Foo', 'boz', '$bar')->shouldReturn('Baz'); + } + + public function it_returns_false_for_typehints_that_have_not_been_added() + { + $this->lookup('Foo', 'boz', '$bar')->shouldBe(false); + } + + public function it_throws_invalid_argument_exceptions() + { + $e = new DisallowedNonObjectTypehintException(); + + $this->addInvalid('Foo', 'boz', '$bar', $e); + + $this->shouldThrow($e)->duringLookup('Foo', 'boz', '$bar'); + } +} diff --git a/spec/PhpSpec/Loader/Transformer/TypeHintRewriterSpec.php b/spec/PhpSpec/Loader/Transformer/TypeHintRewriterSpec.php new file mode 100644 index 0000000..9ec1af8 --- /dev/null +++ b/spec/PhpSpec/Loader/Transformer/TypeHintRewriterSpec.php @@ -0,0 +1,28 @@ +beConstructedWith($rewriter); + } + + public function it_is_a_transformer() + { + $this->shouldHaveType('PhpSpec\Loader\SpecTransformer'); + } + + public function it_delegates_transforming_to_rewriter(TypeHintRewriter $rewriter) + { + $rewriter->rewrite('willReturn('hello world'); + + $this->transform('shouldReturn('hello world'); + } +} diff --git a/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php b/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php new file mode 100644 index 0000000..d2d9479 --- /dev/null +++ b/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php @@ -0,0 +1,558 @@ +srcPath = realpath(__DIR__.'/../../../../vendor/phpspec/phpspec/src'); + $this->specPath = realpath(__DIR__.'/../../../../'); + + $this->beConstructedWith($fs); + } + + public function it_is_a_locator() + { + $this->shouldBeAnInstanceOf('PhpSpec\Locator\ResourceLocator'); + } + + public function its_priority_is_zero() + { + $this->getPriority()->shouldReturn(0); + } + + public function it_generates_fullSrcPath_from_srcPath_plus_namespace(Filesystem $fs) + { + $this->beConstructedWith($fs, 'Cust\Ns', 'spec', dirname(__DIR__), __DIR__); + + $this->getFullSrcPath()->shouldReturn( + dirname(__DIR__).DIRECTORY_SEPARATOR.'Cust'.DIRECTORY_SEPARATOR.'Ns'.DIRECTORY_SEPARATOR + ); + } + + public function it_generates_fullSrcPath_from_srcPath_plus_namespace_cutting_psr4_prefix(Filesystem $fs) + { + $this->beConstructedWith($fs, 'psr4\prefix\Cust\Ns', 'spec', dirname(__DIR__), __DIR__, 'psr4\prefix'); + + $this->getFullSrcPath()->shouldReturn( + dirname(__DIR__).DIRECTORY_SEPARATOR.'Cust'.DIRECTORY_SEPARATOR.'Ns'.DIRECTORY_SEPARATOR + ); + } + + public function it_generates_proper_fullSrcPath_even_from_empty_namespace(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', dirname(__DIR__), __DIR__); + + $this->getFullSrcPath()->shouldReturn(dirname(__DIR__).DIRECTORY_SEPARATOR); + } + + public function it_should_not_have_backslash_on_missing_prefix(Filesystem $fs) + { + $this->beConstructedWith($fs, 'Cust\Ns', '', dirname(__DIR__), __DIR__); + + $this->getSpecNamespace()->shouldReturn('Cust\Ns\\'); + + $this->getFullSpecPath()->shouldReturn(__DIR__.DIRECTORY_SEPARATOR.'Cust'.DIRECTORY_SEPARATOR.'Ns'.DIRECTORY_SEPARATOR); + } + + public function it_generates_fullSpecPath_from_specPath_plus_namespace(Filesystem $fs) + { + $this->beConstructedWith($fs, 'C\N', 'spec', dirname(__DIR__), __DIR__); + + $this->getFullSpecPath()->shouldReturn( + __DIR__.DIRECTORY_SEPARATOR.'spec'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'N'.DIRECTORY_SEPARATOR + ); + } + + public function it_generates_fullSpecPath_from_specPath_plus_namespace_cutting_psr4_prefix(Filesystem $fs) + { + $this->beConstructedWith($fs, 'p\pf\C\N', 'spec', dirname(__DIR__), __DIR__, 'p\pf'); + + $this->getFullSpecPath()->shouldReturn( + __DIR__.DIRECTORY_SEPARATOR.'spec'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'N'.DIRECTORY_SEPARATOR + ); + } + + public function it_generates_proper_fullSpecPath_even_from_empty_src_namespace(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', dirname(__DIR__), __DIR__); + + $this->getFullSpecPath()->shouldReturn( + __DIR__.DIRECTORY_SEPARATOR.'spec'.DIRECTORY_SEPARATOR + ); + } + + public function it_stores_srcNamespace_it_was_constructed_with(Filesystem $fs) + { + $this->beConstructedWith($fs, 'Some\Namespace', 'spec', dirname(__DIR__), __DIR__); + + $this->getSrcNamespace()->shouldReturn('Some\Namespace\\'); + } + + public function it_trims_srcNamespace_during_construction(Filesystem $fs) + { + $this->beConstructedWith($fs, '\\Some\Namespace\\', 'spec', dirname(__DIR__), __DIR__); + + $this->getSrcNamespace()->shouldReturn('Some\Namespace\\'); + } + + public function it_supports_empty_namespace_argument(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', dirname(__DIR__), __DIR__); + + $this->getSrcNamespace()->shouldReturn(''); + } + + public function it_generates_specNamespace_using_srcNamespace_and_specPrefix(Filesystem $fs) + { + $this->beConstructedWith($fs, 'Some\Namespace', 'spec', dirname(__DIR__), __DIR__); + + $this->getSpecNamespace()->shouldReturn('spec\Some\Namespace\\'); + } + + public function it_trims_specNamespace_during_construction(Filesystem $fs) + { + $this->beConstructedWith($fs, '\\Some\Namespace\\', '\\spec\\ns\\', dirname(__DIR__), __DIR__); + + $this->getSpecNamespace()->shouldReturn('spec\ns\Some\Namespace\\'); + } + + public function it_generates_proper_specNamespace_for_empty_srcNamespace(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', dirname(__DIR__), __DIR__); + + $this->getSpecNamespace()->shouldReturn('spec\\'); + } + + public function it_finds_all_resources_from_tracked_specPath(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, '', 'spec', dirname(__DIR__), __DIR__); + $path = __DIR__.DIRECTORY_SEPARATOR.'spec'.DIRECTORY_SEPARATOR; + $filePath = __DIR__.$this->convert_to_path('/spec/Some/ClassSpec.php'); + + $fs->pathExists($path)->willReturn(true); + $fs->findSpecFilesIn($path)->willReturn([$file]); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $resources = $this->getAllResources(); + $resources->shouldHaveCount(1); + $resources[0]->getSpecClassname()->shouldReturn('spec\Some\ClassSpec'); + } + + public function it_returns_empty_array_if_tracked_specPath_does_not_exist(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', dirname(__DIR__), __DIR__); + $path = __DIR__.DIRECTORY_SEPARATOR.'spec'.DIRECTORY_SEPARATOR; + + $fs->pathExists($path)->willReturn(false); + + $resources = $this->getAllResources(); + $resources->shouldHaveCount(0); + } + + public function it_supports_folder_queries_in_srcPath(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsQuery($this->srcPath.'/PhpSpec')->shouldReturn(true); + } + + public function it_supports_srcPath_queries(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsQuery($this->srcPath)->shouldReturn(true); + } + + public function it_supports_file_queries_in_srcPath(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsQuery( + realpath($this->srcPath.'/PhpSpec/Locator/PSR0/PSR0Locator.php') + )->shouldReturn(true); + } + + public function it_supports_folder_queries_in_specPath(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsQuery($this->specPath.'/spec/PhpSpec')->shouldReturn(true); + } + + public function it_supports_specPath_queries(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsQuery($this->specPath.'/spec')->shouldReturn(true); + } + + public function it_supports_file_queries_in_specPath(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsQuery( + realpath($this->specPath.'/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php') + )->shouldReturn(true); + } + + public function it_does_not_support_any_other_queries(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsQuery('/')->shouldReturn(false); + } + + public function it_finds_spec_resources_via_srcPath(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/ContainerSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn(true); + $fs->findSpecFilesIn($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn([$file]); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $resources = $this->findResources($this->srcPath); + $resources->shouldHaveCount(1); + $resources[0]->getSrcClassname()->shouldReturn('PhpSpec\Container'); + } + + public function it_finds_spec_resources_with_classname_underscores_via_srcPath(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Some/ClassSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn(true); + $fs->findSpecFilesIn($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn([$file]); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $resources = $this->findResources($this->srcPath); + $resources->shouldHaveCount(1); + $resources[0]->getSrcClassname()->shouldReturn('PhpSpec\Some_Class'); + } + + public function it_finds_spec_resources_via_fullSrcPath(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Console/AppSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/Console/'))->willReturn(true); + $fs->findSpecFilesIn($this->specPath.$this->convert_to_path('/spec/PhpSpec/Console/'))->willReturn([$file]); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $resources = $this->findResources($this->srcPath.$this->convert_to_path('/PhpSpec/Console')); + $resources->shouldHaveCount(1); + $resources[0]->getSrcClassname()->shouldReturn('PhpSpec\Console\App'); + } + + public function it_finds_spec_resources_via_specPath(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Runner/ExampleRunnerSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/Runner/'))->willReturn(true); + $fs->findSpecFilesIn($this->specPath.$this->convert_to_path('/spec/PhpSpec/Runner/'))->willReturn([$file]); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $resources = $this->findResources($this->specPath.$this->convert_to_path('/spec/PhpSpec/Runner')); + $resources->shouldHaveCount(1); + $resources[0]->getSrcClassname()->shouldReturn('PhpSpec\Runner\ExampleRunner'); + } + + public function it_finds_single_spec_via_srcPath(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php'))->willReturn(true); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $resources = $this->findResources($this->srcPath.$this->convert_to_path('/PhpSpec/Locator/PSR0/PSR0Locator.php')); + $resources->shouldHaveCount(1); + $resources[0]->getSrcClassname()->shouldReturn('PhpSpec\Locator\PSR0\PSR0Locator'); + } + + public function it_finds_single_spec_via_specPath(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/Locator/PSR0/PSR0LocatorSpec.php'))->willReturn(true); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $resources = $this->findResources($filePath); + $resources->shouldHaveCount(1); + $resources[0]->getSrcClassname()->shouldReturn('PhpSpec\Locator\PSR0\PSR0Locator'); + } + + public function it_returns_empty_array_if_nothing_found(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $fs->pathExists($this->specPath.'/spec/PhpSpec/App/')->willReturn(false); + + $resources = $this->findResources($this->srcPath.'/PhpSpec/App'); + $resources->shouldHaveCount(0); + } + + public function it_throws_an_exception_on_no_class_definition(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Some/ClassSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn(true); + $fs->findSpecFilesIn($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn([$file]); + $fs->getFileContents($filePath)->willReturn('no class definition'); + $file->getRealPath()->willReturn($filePath); + + $exception = new \RuntimeException(sprintf('Spec file "%s" does not contains any class definition.', $filePath)); + + $this->shouldThrow($exception)->duringFindResources($this->srcPath); + } + + public function it_does_not_throw_an_exception_on_no_class_definition_if_file_not_suffixed_with_spec(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Some/Class.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn(true); + $fs->findSpecFilesIn($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn([]); + $fs->getFileContents($filePath)->willReturn('no class definition'); + $file->getRealPath()->willReturn($filePath); + + $exception = new \RuntimeException('Spec file does not contains any class definition.'); + + $this->shouldNotThrow($exception)->duringFindResources($this->srcPath); + } + + public function it_throws_an_exception_when_spec_class_not_in_the_base_specs_namespace(Filesystem $fs, SplFileInfo $file) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $filePath = $this->specPath.$this->convert_to_path('/spec/PhpSpec/Some/ClassSpec.php'); + + $fs->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn(true); + $fs->findSpecFilesIn($this->specPath.$this->convert_to_path('/spec/PhpSpec/'))->willReturn([$file]); + $fs->getFileContents($filePath)->willReturn(''); + $file->getRealPath()->willReturn($filePath); + + $exception = new \RuntimeException('Spec class `InvalidSpecNamespace\\PhpSpec\\ServiceContainer` must be in the base spec namespace `spec\\PhpSpec\\`.'); + + $this->shouldThrow($exception)->duringFindResources($this->srcPath); + } + + public function it_supports_classes_from_srcNamespace(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsClass('PhpSpec\ServiceContainer')->shouldReturn(true); + } + + public function it_supports_backslashed_classes_from_srcNamespace(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsClass('PhpSpec/ServiceContainer')->shouldReturn(true); + } + + public function it_supports_classes_from_specNamespace(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsClass('spec\PhpSpec\ServiceContainer')->shouldReturn(true); + } + + public function it_supports_backslashed_classes_from_specNamespace(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsClass('spec/PhpSpec/ServiceContainer')->shouldReturn(true); + } + + public function it_supports_any_class_if_srcNamespace_is_empty(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', $this->srcPath, $this->specPath); + + $this->supportsClass('ServiceContainer')->shouldReturn(true); + } + + public function it_does_not_support_anything_else(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $this->supportsClass('Acme\Any')->shouldReturn(false); + } + + public function it_creates_resource_from_src_class(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $resource = $this->createResource('PhpSpec\Console\Application'); + + $resource->getSrcClassname()->shouldReturn('PhpSpec\Console\Application'); + $resource->getSpecClassname()->shouldReturn('spec\PhpSpec\Console\ApplicationSpec'); + } + + public function it_creates_resource_from_backslashed_src_class(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $resource = $this->createResource('PhpSpec/Console/Application'); + + $resource->getSrcClassname()->shouldReturn('PhpSpec\Console\Application'); + $resource->getSpecClassname()->shouldReturn('spec\PhpSpec\Console\ApplicationSpec'); + } + + public function it_creates_resource_from_spec_class(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $resource = $this->createResource('spec\PhpSpec\Console\Application'); + + $resource->getSrcClassname()->shouldReturn('PhpSpec\Console\Application'); + $resource->getSpecClassname()->shouldReturn('spec\PhpSpec\Console\ApplicationSpec'); + } + + public function it_creates_resource_from_backslashed_spec_class(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $resource = $this->createResource('spec/PhpSpec/Console/Application'); + + $resource->getSrcClassname()->shouldReturn('PhpSpec\Console\Application'); + $resource->getSpecClassname()->shouldReturn('spec\PhpSpec\Console\ApplicationSpec'); + } + + public function it_creates_resource_from_src_class_even_if_srcNamespace_is_empty(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', $this->srcPath, $this->specPath); + + $resource = $this->createResource('Console\Application'); + + $resource->getSrcClassname()->shouldReturn('Console\Application'); + $resource->getSpecClassname()->shouldReturn('spec\Console\ApplicationSpec'); + } + + public function it_creates_resource_from_spec_class_with_leading_backslash(Filesystem $fs) + { + $this->beConstructedWith($fs, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + + $resource = $this->createResource('\PhpSpec\Console\Application'); + + $resource->getSrcClassname()->shouldReturn('PhpSpec\Console\Application'); + $resource->getSpecClassname()->shouldReturn('spec\PhpSpec\Console\ApplicationSpec'); + } + + public function it_throws_an_exception_on_non_PSR0_resource(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', $this->srcPath, $this->specPath); + + $exception = new \InvalidArgumentException( + 'String "Non-PSR0/Namespace" is not a valid class name.'.PHP_EOL. + 'Please see reference document: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md' + ); + + $this->shouldThrow($exception)->duringCreateResource('Non-PSR0/Namespace'); + } + + public function it_throws_an_exception_on_PSR0_resource_with_double_backslash(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', $this->srcPath, $this->specPath); + + $exception = new \InvalidArgumentException( + 'String "NonPSR0\\\\Namespace" is not a valid class name.'.PHP_EOL. + 'Please see reference document: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md' + ); + + $this->shouldThrow($exception)->duringCreateResource('NonPSR0\\\\Namespace'); + } + + public function it_throws_an_exception_on_PSR0_resource_with_slash_on_the_end(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', $this->srcPath, $this->specPath); + + $exception = new \InvalidArgumentException( + 'String "Namespace/" is not a valid class name.'.PHP_EOL. + 'Please see reference document: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md' + ); + + $this->shouldThrow($exception)->duringCreateResource('Namespace/'); + } + + public function it_throws_an_exception_on_PSR0_resource_with_line_breaks_at_end(Filesystem $fs) + { + $this->beConstructedWith($fs, '', 'spec', $this->srcPath, $this->specPath); + + $exception = new \InvalidArgumentException( + 'String "Namespace\Classname'.PHP_EOL.'" is not a valid class name.'.PHP_EOL. + 'Please see reference document: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md' + ); + + $this->shouldThrow($exception)->duringCreateResource('Namespace\Classname'.PHP_EOL); + } + + public function it_throws_an_exception_on_PSR4_prefix_not_matching_namespace(Filesystem $fs) + { + $exception = new \InvalidArgumentException( + 'PSR4 prefix doesn\'t match given class namespace.'.PHP_EOL + ); + + $this->shouldThrow($exception)->during('__construct', [$fs, 'p\pf\N\S', 'spec', $this->srcPath, $this->specPath, 'wrong\prefix']); + } + + public function it_supports_psr0_namespace_queries(Filesystem $filesystem) + { + $this->beConstructedWith($filesystem, '', 'spec', $this->srcPath, $this->specPath); + $filesystem->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/Console/ApplicationSpec.php'))->willReturn(true); + $this->supportsQuery('PhpSpec\\Console\\Application')->shouldReturn(true); + } + + public function it_supports_psr0_namespace_queries_with_a_namespace_prefix(Filesystem $filesystem) + { + $this->beConstructedWith($filesystem, 'PhpSpec', 'spec', $this->srcPath, $this->specPath); + $filesystem->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/Console/ApplicationSpec.php'))->willReturn(true); + $this->supportsQuery('Console\\Application')->shouldReturn(true); + } + + public function it_supports_psr4_namespace_queries(Filesystem $filesystem) + { + $this->beConstructedWith($filesystem, 'Test\\Namespace\\PhpSpec', 'spec', $this->srcPath, $this->specPath, 'Test\\Namespace'); + $filesystem->pathExists($this->specPath.$this->convert_to_path('/spec/PhpSpec/Console/ApplicationSpec.php'))->willReturn(true); + $this->supportsQuery('Test\\Namespace\\PhpSpec\\Console\\Application')->shouldReturn(true); + } + + private function convert_to_path($path) + { + if ('/' === DIRECTORY_SEPARATOR) { + return $path; + } + + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } +} diff --git a/spec/PhpSpec/Locator/PSR0/PSR0ResourceSpec.php b/spec/PhpSpec/Locator/PSR0/PSR0ResourceSpec.php new file mode 100644 index 0000000..bdd3135 --- /dev/null +++ b/spec/PhpSpec/Locator/PSR0/PSR0ResourceSpec.php @@ -0,0 +1,120 @@ +beConstructedWith(['usr', 'lib', 'config'], $locator); + + $locator->isPSR4()->willReturn(false); + } + + public function it_uses_last_segment_as_name() + { + $this->getName()->shouldReturn('config'); + } + + public function it_uses_last_segment_plus_Spec_suffix_as_specName() + { + $this->getSpecName()->shouldReturn('configSpec'); + } + + public function it_is_a_resource() + { + $this->shouldBeAnInstanceOf('PhpSpec\Locator\Resource'); + } + + public function it_generates_src_filename_from_provided_parts_using_locator($locator) + { + $locator->getFullSrcPath()->willReturn('/local/'); + + $this->getSrcFilename()->shouldReturn('/local/usr'.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'config.php'); + } + + public function it_generates_src_namespace_from_provided_parts_using_locator($locator) + { + $locator->getSrcNamespace()->willReturn('Local\\'); + + $this->getSrcNamespace()->shouldReturn('Local\usr\lib'); + } + + public function it_generates_proper_src_namespace_even_if_there_is_only_one_part($locator) + { + $this->beConstructedWith(['config'], $locator); + $locator->getSrcNamespace()->willReturn('Local\\'); + + $this->getSrcNamespace()->shouldReturn('Local'); + } + + public function it_generates_src_classname_from_provided_parts_using_locator($locator) + { + $locator->getSrcNamespace()->willReturn('Local\\'); + + $this->getSrcClassname()->shouldReturn('Local\usr\lib\config'); + } + + public function it_generates_proper_src_classname_for_empty_locator_namespace($locator) + { + $locator->getSrcNamespace()->willReturn(''); + + $this->getSrcClassname()->shouldReturn('usr\lib\config'); + } + + public function it_generates_spec_filename_from_provided_parts_using_locator($locator) + { + $locator->getFullSpecPath()->willReturn('/local/spec/'); + + $this->getSpecFilename()->shouldReturn('/local/spec/usr'.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'configSpec.php'); + } + + public function it_generates_spec_namespace_from_provided_parts_using_locator($locator) + { + $locator->getSpecNamespace()->willReturn('spec\Local\\'); + + $this->getSpecNamespace()->shouldReturn('spec\Local\usr\lib'); + } + + public function it_generates_proper_spec_namespace_even_if_there_is_only_one_part($locator) + { + $this->beConstructedWith(['config'], $locator); + $locator->getSpecNamespace()->willReturn('spec\Local\\'); + + $this->getSpecNamespace()->shouldReturn('spec\Local'); + } + + public function it_generates_spec_classname_from_provided_parts_using_locator($locator) + { + $locator->getSpecNamespace()->willReturn('spec\Local\\'); + + $this->getSpecClassname()->shouldReturn('spec\Local\usr\lib\configSpec'); + } + + public function it_does_not_split_underscores_when_locator_has_psr4_prefix($locator) + { + $this->beConstructedWith(['usr', 'lib', 'config_test'], $locator); + + $locator->getFullSrcPath()->willReturn($this->convert_to_path('/local/')); + $locator->getFullSpecPath()->willReturn($this->convert_to_path('/local/spec/')); + $locator->isPSR4()->willReturn(true); + + $this->getSrcFilename()->shouldReturn($this->convert_to_path('/local/usr/lib/config_test.php')); + $this->getSpecFilename()->shouldReturn($this->convert_to_path('/local/spec/usr/lib/config_testSpec.php')); + } + + private function convert_to_path($path) + { + if ('/' === DIRECTORY_SEPARATOR) { + return $path; + } + + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } +} diff --git a/spec/PhpSpec/Locator/PrioritizedResourceManagerSpec.php b/spec/PhpSpec/Locator/PrioritizedResourceManagerSpec.php new file mode 100644 index 0000000..b6962f5 --- /dev/null +++ b/spec/PhpSpec/Locator/PrioritizedResourceManagerSpec.php @@ -0,0 +1,136 @@ +getPriority()->willReturn(5); + $locator2->getPriority()->willReturn(10); + } + + public function it_locates_resources_using_all_registered_locators( + $locator1, + $locator2, + Resource $resource1, + Resource $resource2, + Resource $resource3 + ) { + $this->registerLocator($locator1); + $this->registerLocator($locator2); + + $locator1->supportsQuery('s:query')->willReturn(true); + $locator1->findResources('s:query')->willReturn([$resource3, $resource2]); + $locator2->supportsQuery('s:query')->willReturn(true); + $locator2->findResources('s:query')->willReturn([$resource1]); + + $resource1->getSpecClassname()->willReturn('Some\Spec1'); + $resource2->getSpecClassname()->willReturn('Some\Spec2'); + $resource3->getSpecClassname()->willReturn('Some\Spec3'); + + $this->locateResources('s:query')->shouldReturn([$resource1, $resource3, $resource2]); + } + + public function it_locates_all_locators_resources_if_query_string_is_empty( + $locator1, + $locator2, + Resource $resource1, + Resource $resource2, + Resource $resource3 + ) { + $this->registerLocator($locator1); + $this->registerLocator($locator2); + + $locator1->getAllResources()->willReturn([$resource3, $resource2]); + $locator2->getAllResources()->willReturn([$resource1]); + + $resource1->getSpecClassname()->willReturn('Some\Spec1'); + $resource2->getSpecClassname()->willReturn('Some\Spec2'); + $resource3->getSpecClassname()->willReturn('Some\Spec3'); + + $this->locateResources('')->shouldReturn([$resource1, $resource3, $resource2]); + } + + public function it_returns_empty_array_if_registered_locators_do_not_support_query($locator1) + { + $this->registerLocator($locator1); + + $locator1->supportsQuery('s:query')->willReturn(false); + $locator1->findResources('s:query')->shouldNotBeCalled(); + + $this->locateResources('s:query')->shouldReturn([]); + } + + public function it_creates_resource_from_classname_using_locator_with_highest_priority( + $locator1, + $locator2, + Resource $resource1, + Resource $resource2 + ) { + $this->registerLocator($locator1); + $this->registerLocator($locator2); + + $locator1->supportsClass('Some\Class')->willReturn(true); + $locator1->createResource('Some\Class')->willReturn($resource1); + $locator2->supportsClass('Some\Class')->willReturn(true); + $locator2->createResource('Some\Class')->willReturn($resource2); + + $this->createResource('Some\Class')->shouldReturn($resource2); + } + + public function it_throws_an_exception_if_locators_do_not_support_classname($locator1) + { + $this->registerLocator($locator1); + + $locator1->supportsClass('Some\Class')->willReturn(false); + + $this->shouldThrow('RuntimeException')->duringCreateResource('Some\Class'); + } + + public function it_does_not_allow_two_resources_for_the_same_spec( + $locator1, + $locator2, + Resource $resource1, + Resource $resource2 + ) { + $this->registerLocator($locator1); + $this->registerLocator($locator2); + + $resource1->getSpecClassname()->willReturn('Some\Spec'); + $resource2->getSpecClassname()->willReturn('Some\Spec'); + + $locator1->getAllResources()->willReturn([$resource1]); + $locator2->getAllResources()->willReturn([$resource2]); + + $this->locateResources('')->shouldReturn([$resource2]); + } + + public function it_uses_the_resource_from_the_highest_priority_locator_when_duplicates_occur( + $locator1, + $locator2, + Resource $resource1, + Resource $resource2 + ) { + $locator1->getPriority()->willReturn(2); + $locator2->getPriority()->willReturn(1); + + $this->registerLocator($locator1); + $this->registerLocator($locator2); + + $resource1->getSpecClassname()->willReturn('Some\Spec'); + $resource2->getSpecClassname()->willReturn('Some\Spec'); + + $locator1->getAllResources()->willReturn([$resource1]); + $locator2->getAllResources()->willReturn([$resource2]); + + $this->locateResources('')->shouldReturn([$resource1]); + } +} diff --git a/spec/PhpSpec/Matcher/ApproximatelyMatcherSpec.php b/spec/PhpSpec/Matcher/ApproximatelyMatcherSpec.php new file mode 100644 index 0000000..634ce1f --- /dev/null +++ b/spec/PhpSpec/Matcher/ApproximatelyMatcherSpec.php @@ -0,0 +1,46 @@ +presentValue(Argument::any())->willReturn(41.889240346184, 1.0e-1); + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_supports_various_aliases() + { + $this->supports('beApproximately', 1.0, [1.0, 5])->shouldReturn(true); + $this->supports('beEqualToApproximately', 1.0, [1.0, 5])->shouldReturn(true); + $this->supports('equalApproximately', 1.0, [1.0, 5])->shouldReturn(true); + $this->supports('returnApproximately', 1.0, [1.0, 5])->shouldReturn(true); + } + + public function it_matches_same_float() + { + $this->shouldNotThrow()->duringPositiveMatch('shouldBeApproximately', 1.4444444444, [1.4444444445, 1.0e-9]); + } + + public function it_does_not_match_different_floats() + { + $this->shouldThrow()->duringPositiveMatch('shouldBeApproximately', 1.4444444444, [1.444447777, 1.0e-9]); + } + + public function it_match_floats_with_near_float() + { + $this->shouldNotThrow()->duringPositiveMatch('shouldBeApproximately', 1.4455, [1.4466, 1.0e-2]); + } +} diff --git a/spec/PhpSpec/Matcher/ArrayContainMatcherSpec.php b/spec/PhpSpec/Matcher/ArrayContainMatcherSpec.php new file mode 100644 index 0000000..cf4e87c --- /dev/null +++ b/spec/PhpSpec/Matcher/ArrayContainMatcherSpec.php @@ -0,0 +1,48 @@ +presentValue(Argument::any())->willReturn('countable'); + $presenter->presentString(Argument::any())->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_responds_to_contain() + { + $this->supports('contain', [], [''])->shouldReturn(true); + } + + public function it_matches_array_with_specified_value() + { + $this->shouldNotThrow()->duringPositiveMatch('contain', ['abc'], ['abc']); + } + + public function it_does_not_match_array_without_specified_value() + { + $this->shouldThrow()->duringPositiveMatch('contain', [1,2,3], ['abc']); + $this->shouldThrow('PhpSpec\Exception\Example\FailureException') + ->duringPositiveMatch('contain', [1,2,3], [new \stdClass()]); + } + + public function it_matches_array_without_specified_value() + { + $this->shouldNotThrow()->duringNegativeMatch('contain', [1,2,3], ['abc']); + } +} diff --git a/spec/PhpSpec/Matcher/ArrayCountMatcherSpec.php b/spec/PhpSpec/Matcher/ArrayCountMatcherSpec.php new file mode 100644 index 0000000..67217b9 --- /dev/null +++ b/spec/PhpSpec/Matcher/ArrayCountMatcherSpec.php @@ -0,0 +1,72 @@ +presentValue(Argument::any())->willReturn('countable'); + $presenter->presentString(Argument::any())->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_responds_to_haveCount() + { + $this->supports('haveCount', [], [''])->shouldReturn(true); + } + + public function it_matches_proper_array_count() + { + $this->shouldNotThrow()->duringPositiveMatch('haveCount', [1,2,3], [3]); + } + + public function it_matches_proper_countable_count(ArrayObject $countable) + { + $countable->count()->willReturn(4); + + $this->shouldNotThrow()->duringPositiveMatch('haveCount', $countable, [4]); + } + + public function it_does_not_match_wrong_array_count() + { + $this->shouldThrow(new FailureException('Expected countable to have 2 items, but got 3.')) + ->duringPositiveMatch('haveCount', [1,2,3], [2]); + } + + public function it_does_not_match_proper_countable_count(ArrayObject $countable) + { + $countable->count()->willReturn(5); + + $this->shouldThrow(new FailureException('Expected countable to have 4 items, but got 5.')) + ->duringPositiveMatch('haveCount', $countable, [4]); + } + + public function it_mismatches_wrong_array_count() + { + $this->shouldNotThrow()->duringNegativeMatch('haveCount', [1,2,3], [2]); + } + + public function it_mismatches_wrong_countable_count(ArrayObject $countable) + { + $countable->count()->willReturn(5); + + $this->shouldNotThrow()->duringNegativeMatch('haveCount', $countable, [4]); + } +} diff --git a/spec/PhpSpec/Matcher/ArrayKeyMatcherSpec.php b/spec/PhpSpec/Matcher/ArrayKeyMatcherSpec.php new file mode 100644 index 0000000..1ac5495 --- /dev/null +++ b/spec/PhpSpec/Matcher/ArrayKeyMatcherSpec.php @@ -0,0 +1,74 @@ +presentValue(Argument::any())->willReturn('countable'); + $presenter->presentString(Argument::any())->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_responds_to_haveKey() + { + $this->supports('haveKey', [], [''])->shouldReturn(true); + } + + public function it_matches_array_with_specified_key() + { + $this->shouldNotThrow()->duringPositiveMatch('haveKey', ['abc' => 123], ['abc']); + } + + public function it_matches_array_with_specified_key_even_if_there_is_no_value() + { + $this->shouldNotThrow()->duringPositiveMatch('haveKey', ['abc' => null], ['abc']); + } + + public function it_matches_ArrayObject_with_provided_offset(ArrayObject $array) + { + $array->offsetExists('abc')->willReturn(true); + + $this->shouldNotThrow()->duringPositiveMatch('haveKey', $array, ['abc']); + } + + public function it_does_not_match_array_without_specified_key() + { + $this->shouldThrow()->duringPositiveMatch('haveKey', [1,2,3], ['abc']); + } + + public function it_does_not_match_ArrayObject_without_provided_offset(ArrayObject $array) + { + $array->offsetExists('abc')->willReturn(false); + + $this->shouldThrow()->duringPositiveMatch('haveKey', $array, ['abc']); + } + + public function it_matches_array_without_specified_key() + { + $this->shouldNotThrow()->duringNegativeMatch('haveKey', [1,2,3], ['abc']); + } + + public function it_matches_ArrayObject_without_specified_offset(ArrayObject $array) + { + $array->offsetExists('abc')->willReturn(false); + + $this->shouldNotThrow()->duringNegativeMatch('haveKey', $array, ['abc']); + } +} diff --git a/spec/PhpSpec/Matcher/ArrayKeyValueMatcherSpec.php b/spec/PhpSpec/Matcher/ArrayKeyValueMatcherSpec.php new file mode 100644 index 0000000..9bc8248 --- /dev/null +++ b/spec/PhpSpec/Matcher/ArrayKeyValueMatcherSpec.php @@ -0,0 +1,114 @@ +presentValue(Argument::any())->will(function ($subject) { + if (is_array($subject[0])) { + return 'array'; + } + if (is_object($subject[0])) { + return 'object'; + } + + return $subject[0]; + }); + $presenter->presentString(Argument::any())->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_responds_to_haveKeyWithValue_with_array_subject() + { + $this->supports('haveKeyWithValue', [], ['', ''])->shouldReturn(true); + } + + public function it_responds_to_haveKeyWithValue_with_array_access_subject() + { + $this->supports('haveKeyWithValue', new \ArrayIterator(), ['', ''])->shouldReturn(true); + } + + public function it_does_not_respond_to_haveKeyWithValue_with_non_array_subject() + { + $this->supports('haveKeyWithValue', null, ['', ''])->shouldReturn(false); + } + + public function it_matches_array_with_correct_value_for_specified_key() + { + $this->shouldNotThrow()->duringPositiveMatch('haveKeyWithValue', ['abc' => 123], ['abc', 123]); + } + + public function it_does_not_match_array_with_wrong_value_for_specified_key() + { + $this->shouldThrow(new FailureException('Expected array to have value 456 for abc key, but found 123.'))->duringPositiveMatch('haveKeyWithValue', ['abc' => 123], ['abc', 456]); + } + + public function it_does_not_match_array_with_missing_key() + { + $this->shouldThrow(new FailureException('Expected array to have key abc, but it didn\'t.'))->duringPositiveMatch('haveKeyWithValue', [], ['abc', 123]); + } + + public function it_matches_ArrayObject_with_correct_value_for_specified_offset(ArrayObject $array) + { + $array->offsetExists('abc')->willReturn(true); + $array->offsetGet('abc')->willReturn(123); + + $this->shouldNotThrow()->duringPositiveMatch('haveKeyWithValue', $array, ['abc', 123]); + } + + public function it_does_not_match_ArrayObject_with_missing_key(ArrayObject $array) + { + $this->shouldThrow(new FailureException('Expected object to have key abc, but it didn\'t.'))->duringPositiveMatch('haveKeyWithValue', $array, ['abc', 123]); + } + + public function it_does_not_match_ArrayObject_with_wrong_value_for_specified_offset(ArrayObject $array) + { + $array->offsetExists('abc')->willReturn(true); + $array->offsetGet('abc')->willReturn(123); + + $this->shouldThrow(new FailureException('Expected object to have value 456 for abc key, but found 123.'))->duringPositiveMatch('haveKeyWithValue', $array, ['abc', 456]); + } + + public function it_matches_array_without_specified_key() + { + $this->shouldNotThrow()->duringNegativeMatch('haveKeyWithValue', [1,2,3], ['abc', 123]); + } + + public function it_matches_array_with_invalid_key_value() + { + $this->shouldNotThrow()->duringNegativeMatch('haveKeyWithValue', ['abc' => 456], ['abc', 123]); + } + + public function it_matches_ArrayObject_without_specified_offset(ArrayObject $array) + { + $array->offsetExists('abc')->willReturn(false); + + $this->shouldNotThrow()->duringNegativeMatch('haveKeyWithValue', $array, ['abc', 123]); + } + + public function it_matches_ArrayObject_with_invalid_key_value(ArrayObject $array) + { + $array->offsetExists('abc')->willReturn(true); + $array->offsetGet('abc')->willReturn(456); + + $this->shouldNotThrow()->duringNegativeMatch('haveKeyWithValue', $array, ['abc', 123]); + } +} diff --git a/spec/PhpSpec/Matcher/CallbackMatcherSpec.php b/spec/PhpSpec/Matcher/CallbackMatcherSpec.php new file mode 100644 index 0000000..b32df0d --- /dev/null +++ b/spec/PhpSpec/Matcher/CallbackMatcherSpec.php @@ -0,0 +1,55 @@ +presentValue(Argument::any())->willReturn('val'); + $presenter->presentString(Argument::any())->willReturnArgument(); + + $this->beConstructedWith('custom', function () { + }, $presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_supports_same_alias_it_was_constructed_with() + { + $this->supports('custom', [], [])->shouldReturn(true); + } + + public function it_does_not_support_anything_else() + { + $this->supports('anything_else', [], [])->shouldReturn(false); + } + + public function it_matches_if_callback_returns_true($presenter) + { + $this->beConstructedWith('custom', function () { + return true; + }, $presenter); + + $this->shouldNotThrow()->duringPositiveMatch('custom', [], []); + } + + public function it_does_not_match_if_callback_returns_false($presenter) + { + $this->beConstructedWith('custom', function () { + return false; + }, $presenter); + + $this->shouldThrow()->duringPositiveMatch('custom', [], []); + } +} diff --git a/spec/PhpSpec/Matcher/ComparisonMatcherSpec.php b/spec/PhpSpec/Matcher/ComparisonMatcherSpec.php new file mode 100644 index 0000000..bd4cec2 --- /dev/null +++ b/spec/PhpSpec/Matcher/ComparisonMatcherSpec.php @@ -0,0 +1,108 @@ +presentValue(Argument::any())->willReturn('val1', 'val2'); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_responds_to_beLike() + { + $this->supports('beLike', '', [''])->shouldReturn(true); + } + + public function it_matches_empty_string_using_comparison_operator() + { + $this->shouldNotThrow()->duringPositiveMatch('beLike', '', ['']); + } + + public function it_matches_not_empty_string_using_comparison_operator() + { + $this->shouldNotThrow()->duringPositiveMatch('beLike', 'chuck', ['chuck']); + } + + public function it_matches_empty_string_with_emptish_values_using_comparison_operator() + { + $this->shouldNotThrow()->duringPositiveMatch('beLike', '', [0]); + } + + public function it_matches_zero_with_emptish_values_using_comparison_operator() + { + $this->shouldNotThrow()->duringPositiveMatch('beLike', 0, ['']); + } + + public function it_matches_null_with_emptish_values_using_comparison_operator() + { + $this->shouldNotThrow()->duringPositiveMatch('beLike', null, ['']); + } + + public function it_matches_false_with_emptish_values_using_comparison_operator() + { + $this->shouldNotThrow()->duringPositiveMatch('beLike', false, ['']); + } + + public function it_does_not_match_non_empty_different_value() + { + $this->shouldThrow(new FailureException('Expected val1, but got val2.')) + ->duringPositiveMatch('beLike', 'one_value', ['different_value']); + } + + public function it_mismatches_empty_string_using_comparison_operator() + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('beLike', '', ['']); + } + + public function it_mismatches_not_empty_string_using_comparison_operator($matcher) + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('beLike', 'chuck', ['chuck']); + } + + public function it_mismatches_empty_string_with_emptish_values_using_comparison_operator() + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('beLike', '', ['']); + } + + public function it_mismatches_zero_with_emptish_values_using_comparison_operator() + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('beLike', 0, ['']); + } + + public function it_mismatches_null_with_emptish_values_using_comparison_operator() + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('beLike', null, ['']); + } + + public function it_mismatches_false_with_emptish_values_using_comparison_operator() + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('beLike', false, ['']); + } + + public function it_mismatches_on_non_empty_different_value() + { + $this->shouldNotThrow()->duringNegativeMatch('beLike', 'one_value', ['another']); + } +} diff --git a/spec/PhpSpec/Matcher/IdentityMatcherSpec.php b/spec/PhpSpec/Matcher/IdentityMatcherSpec.php new file mode 100644 index 0000000..2641342 --- /dev/null +++ b/spec/PhpSpec/Matcher/IdentityMatcherSpec.php @@ -0,0 +1,123 @@ +presentValue(Argument::any())->willReturn('val1', 'val2'); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_responds_to_return() + { + $this->supports('return', '', [''])->shouldReturn(true); + } + + public function it_responds_to_be() + { + $this->supports('be', '', [''])->shouldReturn(true); + } + + public function it_responds_to_equal() + { + $this->supports('equal', '', [''])->shouldReturn(true); + } + + public function it_responds_to_beEqualTo() + { + $this->supports('beEqualTo', '', [''])->shouldReturn(true); + } + + public function it_matches_empty_strings() + { + $this->shouldNotThrow()->duringPositiveMatch('be', '', ['']); + } + + public function it_matches_not_empty_strings() + { + $this->shouldNotThrow()->duringPositiveMatch('be', 'chuck', ['chuck']); + } + + public function it_does_not_match_empty_string_with_emptish_values() + { + $this->shouldThrow(new FailureException('Expected val1, but got val2.')) + ->duringPositiveMatch('be', '', [false]); + } + + public function it_does_not_match_zero_with_emptish_values() + { + $this->shouldThrow(new FailureException('Expected val1, but got val2.')) + ->duringPositiveMatch('be', 0, [false]); + } + + public function it_does_not_match_null_with_emptish_values() + { + $this->shouldThrow(new FailureException('Expected val1, but got val2.')) + ->duringPositiveMatch('be', null, [false]); + } + + public function it_does_not_match_false_with_emptish_values() + { + $this->shouldThrow(new FailureException('Expected val1, but got val2.')) + ->duringPositiveMatch('be', false, ['']); + } + + public function it_does_not_match_non_empty_different_value() + { + $this->shouldThrow(new FailureException('Expected val1, but got val2.')) + ->duringPositiveMatch('be', 'one', ['two']); + } + + public function it_mismatches_empty_string() + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('be', '', ['']); + } + + public function it_mismatches_not_empty_string($matcher) + { + $this->shouldThrow(new FailureException('Did not expect val1, but got one.')) + ->duringNegativeMatch('be', 'chuck', ['chuck']); + } + + public function it_mismatches_empty_string_with_emptish_values() + { + $this->shouldNotThrow()->duringNegativeMatch('be', '', [false]); + } + + public function it_mismatches_zero_with_emptish_values_using_identity_operator() + { + $this->shouldNotThrow()->duringNegativeMatch('be', 0, [false]); + } + + public function it_mismatches_null_with_emptish_values_using_identity_operator() + { + $this->shouldNotThrow()->duringNegativeMatch('be', null, [false]); + } + + public function it_mismatches_false_with_emptish_values_using_identity_operator() + { + $this->shouldNotThrow()->duringNegativeMatch('be', false, ['']); + } + + public function it_mismatches_on_non_empty_different_value() + { + $this->shouldNotThrow()->duringNegativeMatch('be', 'one', ['two']); + } +} diff --git a/spec/PhpSpec/Matcher/Iterate/IterablesMatcherSpec.php b/spec/PhpSpec/Matcher/Iterate/IterablesMatcherSpec.php new file mode 100644 index 0000000..9932ba9 --- /dev/null +++ b/spec/PhpSpec/Matcher/Iterate/IterablesMatcherSpec.php @@ -0,0 +1,147 @@ + + */ +final class IterablesMatcherSpec extends ObjectBehavior +{ + public function let(Presenter $presenter) + { + $presenter->presentValue(Argument::any())->will(function ($subject) { + return '"'.$subject[0].'"'; + }); + + $this->beConstructedWith($presenter); + } + + public function it_should_throw_an_invalid_argument_exception_if_subject_is_not_iterable() + { + $this + ->shouldThrow(new \InvalidArgumentException('Subject value should be an array or implement \Traversable.')) + ->during('match', ['not iterable', []]) + ; + + $this + ->shouldThrow(new \InvalidArgumentException('Subject value should be an array or implement \Traversable.')) + ->during('match', [9, []]) + ; + + $this + ->shouldThrow(new \InvalidArgumentException('Subject value should be an array or implement \Traversable.')) + ->during('match', [new \stdClass(), []]) + ; + } + + public function it_should_throw_an_invalid_argument_exception_if_expected_value_is_not_iterable() + { + $this + ->shouldThrow(new \InvalidArgumentException('Expected value should be an array or implement \Traversable.')) + ->during('match', [[], 'not iterable']) + ; + + $this + ->shouldThrow(new \InvalidArgumentException('Expected value should be an array or implement \Traversable.')) + ->during('match', [[], 9]) + ; + + $this + ->shouldThrow(new \InvalidArgumentException('Expected value should be an array or implement \Traversable.')) + ->during('match', [[], new \stdClass()]) + ; + } + + public function it_should_throw_an_exception_if_subject_has_less_elements_than_expected() + { + $this + ->shouldThrow(new SubjectHasFewerElementsException()) + ->during('match', [['a' => 'b'], ['a' => 'b', 'c' => 'd']]) + ; + } + + public function it_should_throw_an_exception_if_subject_has_more_elements_than_expected() + { + $this + ->shouldThrow(new SubjectHasMoreElementsException()) + ->during('match', [['a' => 'b', 'c' => 'd'], ['a' => 'b']]) + ; + } + + public function it_should_throw_an_exception_if_subject_element_does_not_match_the_expected_one() + { + $this + ->shouldThrow(new SubjectElementDoesNotMatchException(0, '"a"', '"b"', '"a"', '"c"')) + ->during('match', [['a' => 'b'], ['a' => 'c']]) + ; + + $this + ->shouldThrow(new SubjectElementDoesNotMatchException(0, '"a"', '"b"', '"c"', '"b"')) + ->during('match', [['a' => 'b'], ['c' => 'b']]) + ; + + $this + ->shouldThrow(new SubjectElementDoesNotMatchException(1, '"c"', '"d"', '"c"', '"e"')) + ->during('match', [['a' => 'b', 'c' => 'd'], ['a' => 'b', 'c' => 'e']]) + ; + } + + public function it_should_not_throw_any_exception_if_subject_iterates_as_expected() + { + $this + ->shouldNotThrow() + ->during('match', [['a' => 'b', 'c' => 'd'], ['a' => 'b', 'c' => 'd']]) + ; + + $this + ->shouldNotThrow() + ->during('match', [['a' => 'b', 'c' => 'd'], new \ArrayIterator(['a' => 'b', 'c' => 'd'])]) + ; + + $this + ->shouldNotThrow() + ->during('match', [new \ArrayIterator(['a' => 'b', 'c' => 'd']), ['a' => 'b', 'c' => 'd']]) + ; + + $this + ->shouldNotThrow() + ->during('match', [['a' => 'b', 'c' => 'd'], new \ArrayObject(['a' => 'b', 'c' => 'd'])]) + ; + + $this + ->shouldNotThrow() + ->during('match', [new \ArrayObject(['a' => 'b', 'c' => 'd']), ['a' => 'b', 'c' => 'd']]) + ; + + $this + ->shouldNotThrow() + ->during('match', [$this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['a' => 'b', 'c' => 'd']]) + ; + + $this + ->shouldNotThrow() + ->during('match', [['a' => 'b', 'c' => 'd'], $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd'])]) + ; + } + + /** + * @param array $array + * + * @return \Generator + */ + private function createGeneratorReturningArray(array $array) + { + foreach ($array as $key => $value) { + yield $key => $value; + } + } +} diff --git a/spec/PhpSpec/Matcher/Iterate/SubjectElementDoesNotMatchExceptionSpec.php b/spec/PhpSpec/Matcher/Iterate/SubjectElementDoesNotMatchExceptionSpec.php new file mode 100644 index 0000000..f85b9dc --- /dev/null +++ b/spec/PhpSpec/Matcher/Iterate/SubjectElementDoesNotMatchExceptionSpec.php @@ -0,0 +1,26 @@ +beConstructedWith(42, '"subject key"', '"subject value"', '"expected key"', '"expected value"'); + } + + public function it_is_a_failure_exception() + { + $this->shouldHaveType(FailureException::class); + } + + public function it_has_a_predefined_message() + { + $this->getMessage()->shouldReturn('Expected subject to have element #42 with key "expected key" and value "expected value", but got key "subject key" and value "subject value".'); + } +} diff --git a/spec/PhpSpec/Matcher/Iterate/SubjectHasFewerElementsExceptionSpec.php b/spec/PhpSpec/Matcher/Iterate/SubjectHasFewerElementsExceptionSpec.php new file mode 100644 index 0000000..70ad421 --- /dev/null +++ b/spec/PhpSpec/Matcher/Iterate/SubjectHasFewerElementsExceptionSpec.php @@ -0,0 +1,20 @@ +shouldHaveType(\LengthException::class); + } + + public function it_has_a_predefined_message() + { + $this->getMessage()->shouldReturn('Subject has fewer elements than expected.'); + } +} diff --git a/spec/PhpSpec/Matcher/Iterate/SubjectHasMoreElementsExceptionSpec.php b/spec/PhpSpec/Matcher/Iterate/SubjectHasMoreElementsExceptionSpec.php new file mode 100644 index 0000000..aba852e --- /dev/null +++ b/spec/PhpSpec/Matcher/Iterate/SubjectHasMoreElementsExceptionSpec.php @@ -0,0 +1,20 @@ +shouldHaveType(\LengthException::class); + } + + public function it_has_a_predefined_message() + { + $this->getMessage()->shouldReturn('Subject has more elements than expected.'); + } +} diff --git a/spec/PhpSpec/Matcher/IterateAsMatcherSpec.php b/spec/PhpSpec/Matcher/IterateAsMatcherSpec.php new file mode 100644 index 0000000..5528f51 --- /dev/null +++ b/spec/PhpSpec/Matcher/IterateAsMatcherSpec.php @@ -0,0 +1,159 @@ +presentValue(Argument::any())->will(function ($subject) { + return '"'.$subject[0].'"'; + }); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_iterate() + { + $this->supports('iterateAs', [], [[]])->shouldReturn(true); + + $this->supports('iterateAs', new \ArrayObject([]), [[]])->shouldReturn(true); + $this->supports('iterateAs', new \ArrayIterator([]), [[]])->shouldReturn(true); + $this->supports('iterateAs', $this->createGeneratorReturningArray([]), [[]])->shouldReturn(true); + + $this->supports('iterateAs', [], [new \ArrayIterator([])])->shouldReturn(true); + $this->supports('iterateAs', [], [new \ArrayObject([])])->shouldReturn(true); + $this->supports('iterateAs', [], [$this->createGeneratorReturningArray([])])->shouldReturn(true); + + + $this->supports('yield', [], [[]])->shouldReturn(true); + } + + public function it_positive_matches_generator_while_iterating_the_same() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b', 'c' => 'd']], + ]) + ; + + $this + ->shouldNotThrow() + ->during('positiveMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [$this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd'])], + ]) + ; + } + + public function it_does_not_positive_match_generator_while_not_iterating_the_same() + { + $this + ->shouldThrow(new SubjectElementDoesNotMatchException(1, '"c"', '"d"', '"c"', '"e"')) + ->during('positiveMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b', 'c' => 'e']], + ]) + ; + + $this + ->shouldThrow(new SubjectElementDoesNotMatchException(1, '"c"', '"d"', '"e"', '"d"')) + ->during('positiveMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [$this->createGeneratorReturningArray(['a' => 'b', 'e' => 'd'])], + ]) + ; + + $this + ->shouldThrow(new FailureException('Expected subject to have the same number of elements than matched value, but it has fewer.')) + ->during('positiveMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b', 'c' => 'd', 'e' => 'f']], + ]) + ; + + $this + ->shouldThrow(new FailureException('Expected subject to have the same number of elements than matched value, but it has more.')) + ->during('positiveMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b']], + ]) + ; + } + + public function it_negative_matches_generator_while_not_iterating_the_same() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b', 'c' => 'e']], + ]) + ; + + $this + ->shouldNotThrow() + ->during('negativeMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [$this->createGeneratorReturningArray(['a' => 'b', 'c' => 'e'])], + ]) + ; + } + + public function it_does_not_negative_matches_generator_while_iterating_the_same() + { + $this + ->shouldThrow(FailureException::class) + ->during('negativeMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b', 'c' => 'd']], + ]) + ; + + $this + ->shouldThrow(FailureException::class) + ->during('negativeMatch', [ + 'iterateAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [$this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd'])], + ]) + ; + } + + /** + * @param array $array + * + * @return \Generator + */ + private function createGeneratorReturningArray(array $array) + { + foreach ($array as $key => $value) { + yield $key => $value; + } + } +} diff --git a/spec/PhpSpec/Matcher/IterateLikeMatcherSpec.php b/spec/PhpSpec/Matcher/IterateLikeMatcherSpec.php new file mode 100644 index 0000000..312ee4b --- /dev/null +++ b/spec/PhpSpec/Matcher/IterateLikeMatcherSpec.php @@ -0,0 +1,92 @@ +presentValue(Argument::any())->will(function ($subject) { + return '"'.var_export($subject[0], true).'"'; + }); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_iterate_like() + { + $this->supports('iterateLike', [], [[]])->shouldReturn(true); + + $this->supports('iterateLike', new \ArrayObject([]), [[]])->shouldReturn(true); + $this->supports('iterateLike', new \ArrayIterator([]), [[]])->shouldReturn(true); + $this->supports('iterateLike', $this->createGeneratorReturningArray([]), [[]])->shouldReturn(true); + + $this->supports('iterateLike', [], [new \ArrayIterator([])])->shouldReturn(true); + $this->supports('iterateLike', [], [new \ArrayObject([])])->shouldReturn(true); + $this->supports('iterateLike', [], [$this->createGeneratorReturningArray([])])->shouldReturn(true); + } + + public function it_positive_matches_generator_while_iterating_likes() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', [ + 'iterateLike', + $this->createGeneratorReturningArray([new \stdClass()]), + [[new \stdClass()]], + ]) + ; + + $this + ->shouldNotThrow() + ->during('positiveMatch', [ + 'iterateLike', + $this->createGeneratorReturningArray([new \stdClass()]), + [$this->createGeneratorReturningArray([new \stdClass()])], + ]) + ; + } + + public function it_does_not_positive_match_generator_while_not_iterating_the_same() + { + $first = new \stdClass(); + $first->foo = 'foo'; + + $second = new \stdClass(); + $second->foo = 'bar'; + + $this + ->shouldThrow(SubjectElementDoesNotMatchException::class) + ->during('positiveMatch', [ + 'iterateLike', + $this->createGeneratorReturningArray([$first]), + [$this->createGeneratorReturningArray([$second])], + ]) + ; + } + + /** + * @param array $array + * + * @return \Generator + */ + private function createGeneratorReturningArray(array $array) + { + foreach ($array as $key => $value) { + yield $key => $value; + } + } +} diff --git a/spec/PhpSpec/Matcher/ObjectStateMatcherSpec.php b/spec/PhpSpec/Matcher/ObjectStateMatcherSpec.php new file mode 100644 index 0000000..50e9153 --- /dev/null +++ b/spec/PhpSpec/Matcher/ObjectStateMatcherSpec.php @@ -0,0 +1,121 @@ +presentValue(Argument::any())->willReturn('val1', 'val2'); + $presenter->presentString(Argument::any())->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_infers_matcher_alias_name_from_methods_prefixed_with_is() + { + $subject = new \ReflectionClass($this); + + $this->supports('beAbstract', $subject, [])->shouldReturn(true); + } + + public function it_throws_exception_if_checker_method_not_found() + { + $subject = new \ReflectionClass($this); + + $this->shouldThrow('PhpSpec\Exception\Fracture\MethodNotFoundException') + ->duringPositiveMatch('beSimple', $subject, []); + } + + public function it_matches_if_state_checker_returns_true() + { + $subject = new \ReflectionClass($this); + + $this->shouldNotThrow()->duringPositiveMatch('beUserDefined', $subject, []); + } + + public function it_does_not_match_if_state_checker_returns_false() + { + $subject = new \ReflectionClass($this); + + $this->shouldThrow('PhpSpec\Exception\Example\FailureException') + ->duringPositiveMatch('beFinal', $subject, []); + } + + public function it_infers_matcher_alias_name_from_methods_prefixed_with_has() + { + $subject = new \ReflectionClass($this); + + $this->supports('haveProperty', $subject, ['something'])->shouldReturn(true); + } + + public function it_throws_exception_if_has_checker_method_not_found() + { + $subject = new \ReflectionClass($this); + + $this->shouldThrow('PhpSpec\Exception\Fracture\MethodNotFoundException') + ->duringPositiveMatch('haveAnything', $subject, ['str']); + } + + public function it_matches_if_has_checker_returns_true() + { + $subject = new \ReflectionClass($this); + + $this->shouldNotThrow()->duringPositiveMatch( + 'haveMethod', + $subject, + ['it_matches_if_has_checker_returns_true'] + ); + } + + public function it_does_not_match_if_has_state_checker_returns_false() + { + $subject = new \ReflectionClass($this); + + $this->shouldThrow('PhpSpec\Exception\Example\FailureException') + ->duringPositiveMatch('haveProperty', $subject, ['other']); + } + + public function it_does_not_match_if_subject_is_callable() + { + $subject = function () { + }; + + $this->supports('beCallable', $subject, [])->shouldReturn(false); + } + + public function it_does_not_throw_when_positive_match_true() + { + $subject = new class { + public function isMatched() + { + return true; + } + }; + + $this->positiveMatch('beMatched', $subject, [])->shouldBe(null); + } + + public function it_does_not_throw_when_negative_match_false() + { + $subject = new class { + public function isMatched() + { + return false; + } + }; + + $this->negativeMatch('beMatched', $subject, [])->shouldBe(null); + } +} diff --git a/spec/PhpSpec/Matcher/ScalarMatcherSpec.php b/spec/PhpSpec/Matcher/ScalarMatcherSpec.php new file mode 100644 index 0000000..cf2e5c0 --- /dev/null +++ b/spec/PhpSpec/Matcher/ScalarMatcherSpec.php @@ -0,0 +1,478 @@ +presentValue(Argument::any())->willReturn('val1', 'val2'); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_be_array() + { + $this->supports('beArray', '', [''])->shouldReturn(true); + } + + public function it_matches_array() + { + $this->shouldNotThrow()->duringPositiveMatch('beArray', [], ['']); + } + + public function it_does_not_match_not_array_with_be_array_matcher() + { + $this->shouldThrow()->duringPositiveMatch('beArray', Argument::not([]), ['']); + } + + public function it_mismatches_not_array() + { + $this->shouldNotThrow()->duringNegativeMatch('beArray', Argument::not([]), ['']); + } + + public function it_does_not_mismatch_array() + { + $this->shouldThrow()->duringNegativeMatch('beArray', [], ['']); + } + + public function it_responds_to_be_bool() + { + $this->supports('beBool', '', [''])->shouldReturn(true); + } + + public function it_matches_bool() + { + $this->shouldNotThrow()->duringPositiveMatch('beBool', false, ['']); + } + + public function it_does_not_match_not_bool_with_be_bool_matcher() + { + $this->shouldThrow()->duringPositiveMatch('beBool', Argument::not(false), ['']); + } + + public function it_mismatches_not_bool() + { + $this->shouldNotThrow()->duringNegativeMatch('beBool', Argument::not(false), ['']); + } + + public function it_does_not_mismatch_bool() + { + $this->shouldThrow()->duringNegativeMatch('beBool', false, ['']); + } + + public function it_responds_to_be_boolean() + { + $this->supports('beBoolean', '', [''])->shouldReturn(true); + } + + public function it_matches_boolean() + { + $this->shouldNotThrow()->duringPositiveMatch('beBoolean', false, ['']); + } + + public function it_does_not_match_not_boolean() + { + $this->shouldThrow()->duringPositiveMatch('beBoolean', Argument::not(false), ['']); + } + + public function it_mismatches_not_boolean() + { + $this->shouldNotThrow()->duringNegativeMatch('beBoolean', Argument::not(false), ['']); + } + + public function it_does_not_mismatch_boolean() + { + $this->shouldThrow()->duringNegativeMatch('beBoolean', false, ['']); + } + + public function it_responds_to_be_callable() + { + $this->supports('beCallable', '', [''])->shouldReturn(true); + } + + public function it_matches_callable() + { + $this->shouldNotThrow()->duringPositiveMatch('beCallable', function () { + return true; + }, ['']); + } + + public function it_does_not_match_not_callable() + { + $this->shouldThrow()->duringPositiveMatch('beCallable', Argument::not(function () { + return true; + }), ['']); + } + + public function it_mismatches_not_callable() + { + $this->shouldNotThrow()->duringNegativeMatch('beCallable', Argument::not(function () { + return true; + }), ['']); + } + + public function it_does_not_mismatch_callable() + { + $this->shouldThrow()->duringNegativeMatch('beCallable', function () { + return true; + }, ['']); + } + +// FROM PHP 7.3 - Implement also positive match and negative match +// function it_responds_to_be_countable() +// { +// $this->supports('beCountable', '', [''])->shouldReturn(true); +// } + + public function it_responds_to_be_double() + { + $this->supports('beDouble', '', [''])->shouldReturn(true); + } + + public function it_matches_double() + { + $this->shouldNotThrow()->duringPositiveMatch('beDouble', doubleval(10.5), ['']); + } + + public function it_does_not_match_not_double() + { + $this->shouldThrow()->duringPositiveMatch('beDouble', Argument::not(doubleval(10.5)), ['']); + } + + public function it_mismatches_not_double() + { + $this->shouldNotThrow()->duringNegativeMatch('beDouble', Argument::not(doubleval(10.5)), ['']); + } + + public function it_does_not_mismatches_double() + { + $this->shouldThrow()->duringNegativeMatch('beDouble', doubleval(10.5), ['']); + } + + public function it_responds_to_be_float() + { + $this->supports('beFloat', '', [''])->shouldReturn(true); + } + + public function it_matches_float() + { + $this->shouldNotThrow()->duringPositiveMatch('beFloat', 10.5, ['']); + } + + public function it_does_not_match_not_float() + { + $this->shouldThrow()->duringPositiveMatch('beFloat', Argument::not(10.5), ['']); + } + + public function it_mismatches_not_float() + { + $this->shouldNotThrow()->duringNegativeMatch('beFloat', Argument::not(10.5), ['']); + } + + public function it_does_not_mismatches_float() + { + $this->shouldThrow()->duringNegativeMatch('beFloat', 10.5, ['']); + } + + public function it_responds_to_be_int() + { + $this->supports('beInt', '', [''])->shouldReturn(true); + } + + public function it_matches_int() + { + $this->shouldNotThrow()->duringPositiveMatch('beInt', 1, ['']); + } + + public function it_does_not_match_not_int() + { + $this->shouldThrow()->duringPositiveMatch('beInt', Argument::not(1), ['']); + } + + public function it_mismatches_not_int() + { + $this->shouldNotThrow()->duringNegativeMatch('beInt', Argument::not(1), ['']); + } + + public function it_does_not_mismatches_int() + { + $this->shouldThrow()->duringNegativeMatch('beInt', 1, ['']); + } + + public function it_responds_to_be_integer() + { + $this->supports('beInteger', '', [''])->shouldReturn(true); + } + + public function it_matches_int_with_integer_matcher() + { + $this->shouldNotThrow()->duringPositiveMatch('beInteger', 1, ['']); + } + + public function it_does_not_match_not_integer_match() + { + $this->shouldThrow()->duringPositiveMatch('beInteger', Argument::not(1), ['']); + } + + public function it_mismatches_not_integer() + { + $this->shouldNotThrow()->duringNegativeMatch('beInteger', Argument::not(1), ['']); + } + + public function it_does_not_mismatches_integer() + { + $this->shouldThrow()->duringNegativeMatch('beInteger', 1, ['']); + } + + public function it_responds_to_be_iterable() + { + $this->supports('beIterable', '', [''])->shouldReturn(true); + } + + public function it_matches_iterable() + { + $this->shouldNotThrow()->duringPositiveMatch('beIterable', [], ['']); + } + + public function it_does_not_match_not_iterable() + { + $this->shouldThrow()->duringPositiveMatch('beIterable', Argument::not([]), ['']); + } + + public function it_mismatches_not_iterable() + { + $this->shouldNotThrow()->duringNegativeMatch('beIterable', Argument::not([]), ['']); + } + + public function it_does_not_mismatches_iterable() + { + $this->shouldThrow()->duringNegativeMatch('beIterable', [], ['']); + } + + public function it_responds_to_be_long() + { + $this->supports('beLong', '', [''])->shouldReturn(true); + } + + public function it_matches_long() + { + $this->shouldNotThrow()->duringPositiveMatch('beLong', 4209531264, ['']); + } + + public function it_does_not_match_not_long() + { + $this->shouldThrow()->duringPositiveMatch('beLong', Argument::not(4209531264), ['']); + } + + public function it_mismatches_not_long() + { + $this->shouldNotThrow()->duringNegativeMatch('beLong', Argument::not(4209531264), ['']); + } + + public function it_does_not_mismatches_long() + { + $this->shouldThrow()->duringNegativeMatch('beLong', 4209531264, ['']); + } + + public function it_responds_to_be_null() + { + $this->supports('beNull', '', [''])->shouldReturn(true); + } + + public function it_matches_null() + { + $this->shouldNotThrow()->duringPositiveMatch('beNull', null, ['']); + } + + public function it_does_not_match_not_null() + { + $this->shouldThrow()->duringPositiveMatch('beNull', Argument::not(null), ['']); + } + + public function it_mismatches_not_null() + { + $this->shouldNotThrow()->duringNegativeMatch('beNull', Argument::not(null), ['']); + } + + public function it_does_not_mismatches_null() + { + $this->shouldThrow()->duringNegativeMatch('beNull', null, ['']); + } + + public function it_responds_to_be_numeric() + { + $this->supports('beNumeric', '', [''])->shouldReturn(true); + } + + public function it_matches_numeric_string() + { + $this->shouldNotThrow()->duringPositiveMatch('beNumeric', '123', ['']); + } + + public function it_matches_numeric_number() + { + $this->shouldNotThrow()->duringPositiveMatch('beNumeric', 123, ['']); + } + + public function it_does_not_match_not_numeric_string() + { + $this->shouldThrow()->duringPositiveMatch('beNumeric', Argument::not('123'), ['']); + } + + public function it_does_not_match_not_numeric() + { + $this->shouldThrow()->duringPositiveMatch('beNumeric', Argument::not(123), ['']); + } + + public function it_mismatches_not_number() + { + $this->shouldNotThrow()->duringNegativeMatch('beNumeric', Argument::not(123), ['']); + } + + public function it_does_not_mismatches_number() + { + $this->shouldThrow()->duringNegativeMatch('beNumeric', 123, ['']); + } + + public function it_responds_to_be_object() + { + $this->supports('beObject', '', [''])->shouldReturn(true); + } + + public function it_matches_object() + { + $this->shouldNotThrow()->duringPositiveMatch('beObject', new \stdClass(), ['']); + } + + public function it_does_not_match_not_object() + { + $this->shouldThrow()->duringPositiveMatch('beObject', null, ['']); + } + + public function it_mismatches_not_object() + { + $this->shouldNotThrow()->duringNegativeMatch('beObject', null, ['']); + } + + public function it_does_not_mismatches_object() + { + $this->shouldThrow()->duringNegativeMatch('beObject', new \stdClass(), ['']); + } + + public function it_responds_to_be_real() + { + $this->supports('beReal', '', [''])->shouldReturn(true); + } + + public function it_matches_real() + { + $this->shouldNotThrow()->duringPositiveMatch('beReal', 10.5, ['']); + } + + public function it_does_not_match_not_real() + { + $this->shouldThrow()->duringPositiveMatch('beReal', Argument::not(10.5), ['']); + } + + public function it_mismatches_not_real() + { + $this->shouldNotThrow()->duringNegativeMatch('beReal', Argument::not(10.5), ['']); + } + + public function it_does_not_mismatches_real() + { + $this->shouldThrow()->duringNegativeMatch('beReal', 10.5, ['']); + } + + public function it_responds_to_be_resource() + { + $this->supports('beResource', '', [''])->shouldReturn(true); + } + + public function it_matches_a_resource() + { + $fp = fopen(__FILE__, 'r'); + $this->shouldNotThrow()->duringPositiveMatch('beResource', $fp, ['']); + fclose($fp); + } + + public function it_does_not_match_not_resource() + { + $this->shouldThrow()->duringPositiveMatch('beResource', null, ['']); + } + + public function it_mismatches_not_resource() + { + $this->shouldNotThrow()->duringNegativeMatch('beResource', null, ['']); + } + + public function it_does_not_mismatches_resource() + { + $fp = fopen(__FILE__, 'r'); + $this->shouldThrow()->duringNegativeMatch('beResource', $fp, ['']); + fclose($fp); + } + + public function it_responds_to_be_scalar() + { + $this->supports('beScalar', '', [''])->shouldReturn(true); + } + + public function it_matches_scalar() + { + $this->shouldNotThrow()->duringPositiveMatch('beScalar', 'foo', ['']); + } + + public function it_does_not_match_not_scalar() + { + $this->shouldThrow()->duringPositiveMatch('beResource', null, ['']); + } + + public function it_mismatches_not_scalar() + { + $this->shouldNotThrow()->duringNegativeMatch('beResource', null, ['']); + } + + public function it_does_not_mismatches_scalar() + { + $this->shouldThrow()->duringNegativeMatch('beScalar', 'foo', ['']); + } + + public function it_responds_to_be_string() + { + $this->supports('beString', '', [''])->shouldReturn(true); + } + + public function it_matches_string() + { + $this->shouldNotThrow()->duringPositiveMatch('beString', 'foo', ['']); + } + + public function it_does_not_match_not_string() + { + $this->shouldThrow()->duringPositiveMatch('beString', Argument::not('foo'), ['']); + } + + public function it_mismatches_not_stringt() + { + $this->shouldNotThrow()->duringNegativeMatch('beString', Argument::not('foo'), ['']); + } + + public function it_does_not_mismatches_string() + { + $this->shouldThrow()->duringNegativeMatch('beString', 'foo', ['']); + } +} diff --git a/spec/PhpSpec/Matcher/StartIteratingAsMatcherSpec.php b/spec/PhpSpec/Matcher/StartIteratingAsMatcherSpec.php new file mode 100644 index 0000000..3dc8267 --- /dev/null +++ b/spec/PhpSpec/Matcher/StartIteratingAsMatcherSpec.php @@ -0,0 +1,165 @@ +presentValue(Argument::any())->will(function ($subject) { + return '"'.$subject[0].'"'; + }); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_startIterating() + { + $this->supports('startIteratingAs', [], [[]])->shouldReturn(true); + + $this->supports('startIteratingAs', new \ArrayObject([]), [[]])->shouldReturn(true); + $this->supports('startIteratingAs', new \ArrayIterator([]), [[]])->shouldReturn(true); + $this->supports('startIteratingAs', $this->createGeneratorReturningArray([]), [[]])->shouldReturn(true); + + $this->supports('startIteratingAs', [], [new \ArrayIterator([])])->shouldReturn(true); + $this->supports('startIteratingAs', [], [new \ArrayObject([])])->shouldReturn(true); + $this->supports('startIteratingAs', [], [$this->createGeneratorReturningArray([])])->shouldReturn(true); + + $this->supports('startYielding', [], [[]])->shouldReturn(true); + } + + public function it_positive_matches_generator_while_starting_iterating_the_same() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', [ + 'startIteratingAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b']], + ]) + ; + + $this + ->shouldNotThrow() + ->during('positiveMatch', [ + 'startIteratingAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [$this->createGeneratorReturningArray(['a' => 'b'])], + ]) + ; + } + + public function it_positive_matches_infinite_generator_while_starting_iterating_the_same() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', [ + 'startIteratingAs', + $this->createInfiniteGenerator(), + [[0 => 0, 1 => 1]] + ]) + ; + } + + public function it_does_not_positive_match_generator_while_not_starting_iterating_the_same() + { + $this + ->shouldThrow(new SubjectElementDoesNotMatchException(1, '"c"', '"d"', '"c"', '"e"')) + ->during('positiveMatch', [ + 'startIteratingAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b', 'c' => 'e']], + ]) + ; + } + + public function it_negative_matches_generator_while_not_starting_iterating_the_same() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', [ + 'startIteratingAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b', 'c' => 'e']], + ]) + ; + + $this + ->shouldNotThrow() + ->during('negativeMatch', [ + 'startIteratingAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [$this->createGeneratorReturningArray(['a' => 'b', 'c' => 'e'])], + ]) + ; + } + + public function it_negative_matches_infinite_generator_while_not_starting_iterating_the_same() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', [ + 'startIteratingAs', + $this->createInfiniteGenerator(), + [[0 => 0, 1 => 1, 3 => 3]], + ]) + ; + } + + public function it_does_not_negative_matches_generator_while_starting_iterating_the_same() + { + $this + ->shouldThrow(FailureException::class) + ->during('negativeMatch', [ + 'startIteratingAs', + $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), + [['a' => 'b']], + ]) + ; + + $this + ->shouldThrow(FailureException::class) + ->during('negativeMatch', [ + 'startIteratingAs', + $this->createInfiniteGenerator(), + [[0 => 0, 1 => 1]], + ]) + ; + } + + /** + * @param array $array + * + * @return \Generator + */ + private function createGeneratorReturningArray(array $array) + { + foreach ($array as $key => $value) { + yield $key => $value; + } + } + + /** + * @return \Generator + */ + private function createInfiniteGenerator() + { + for ($i = 0; true; ++$i) { + yield $i => $i; + } + } +} diff --git a/spec/PhpSpec/Matcher/StringContainMatcherSpec.php b/spec/PhpSpec/Matcher/StringContainMatcherSpec.php new file mode 100644 index 0000000..747006a --- /dev/null +++ b/spec/PhpSpec/Matcher/StringContainMatcherSpec.php @@ -0,0 +1,75 @@ + + * (c) Konstantin Kudryashov + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace spec\PhpSpec\Matcher; + +use PhpSpec\Formatter\Presenter\Presenter; +use PhpSpec\ObjectBehavior; +use Prophecy\Argument; + +class StringContainMatcherSpec extends ObjectBehavior +{ + public function let(Presenter $presenter) + { + $presenter->presentString(Argument::type('string'))->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_supports_contain_keyword_string_subject_and_argument() + { + $this->supports('contain', 'hello world', ['llo'])->shouldReturn(true); + } + + public function it_does_not_support_non_string_keyword() + { + $this->supports('contain', [], [])->shouldReturn(false); + } + + public function it_does_not_support_missing_argument() + { + $this->supports('contain', 'hello world', [])->shouldReturn(false); + } + + public function it_does_not_support_non_string_argument() + { + $this->supports('contain', 'hello world', [[]])->shouldReturn(false); + } + + public function it_matches_strings_that_contain_specified_substring() + { + $this->shouldNotThrow()->duringPositiveMatch('contains', 'hello world', ['ello']); + } + + public function it_does_not_match_strings_that_do_not_contain_specified_substring() + { + $this->shouldThrow()->duringPositiveMatch('contains', 'hello world', ['row']); + } + + public function it_matches_strings_that_do_not_contain_specified_substring() + { + $this->shouldNotThrow()->duringNegativeMatch('contains', 'hello world', ['row']); + } + + public function it_does_not_match_strings_that_do_contain_specified_substring() + { + $this->shouldThrow()->duringNegativeMatch('contains', 'hello world', ['ello']); + } +} diff --git a/spec/PhpSpec/Matcher/StringEndMatcherSpec.php b/spec/PhpSpec/Matcher/StringEndMatcherSpec.php new file mode 100644 index 0000000..a4f64a1 --- /dev/null +++ b/spec/PhpSpec/Matcher/StringEndMatcherSpec.php @@ -0,0 +1,55 @@ +presentString(Argument::type('string'))->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_supports_endWith_keyword_and_string_subject() + { + $this->supports('endWith', 'hello, everzet', ['everzet'])->shouldReturn(true); + } + + public function it_does_not_support_anything_else() + { + $this->supports('endWith', [], [])->shouldReturn(false); + } + + public function it_matches_strings_that_end_with_specified_suffix() + { + $this->shouldNotThrow()->duringPositiveMatch('endWith', 'everzet', ['zet']); + } + + public function it_does_not_match_strings_that_do_not_end_with_specified_suffix() + { + $this->shouldThrow()->duringPositiveMatch('endWith', 'everzet', ['tez']); + } + + public function it_matches_strings_that_do_not_end_with_specified_suffix() + { + $this->shouldNotThrow()->duringNegativeMatch('endWith', 'everzet', ['tez']); + } + + public function it_does_not_match_strings_that_do_end_with_specified_suffix() + { + $this->shouldThrow()->duringNegativeMatch('endWith', 'everzet', ['zet']); + } +} diff --git a/spec/PhpSpec/Matcher/StringRegexMatcherSpec.php b/spec/PhpSpec/Matcher/StringRegexMatcherSpec.php new file mode 100644 index 0000000..3774d69 --- /dev/null +++ b/spec/PhpSpec/Matcher/StringRegexMatcherSpec.php @@ -0,0 +1,55 @@ +presentString(Argument::type('string'))->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_supports_match_keyword_and_string_subject() + { + $this->supports('match', 'hello, everzet', ['/hello/'])->shouldReturn(true); + } + + public function it_does_not_support_anything_else() + { + $this->supports('match', [], [])->shouldReturn(false); + } + + public function it_matches_strings_that_match_specified_regex() + { + $this->shouldNotThrow()->duringPositiveMatch('match', 'everzet', ['/ev.*et/']); + } + + public function it_does_not_match_strings_that_do_not_match_specified_regex() + { + $this->shouldThrow()->duringPositiveMatch('match', 'everzet', ['/md/']); + } + + public function it_matches_strings_that_do_not_match_specified_regex() + { + $this->shouldNotThrow()->duringNegativeMatch('match', 'everzet', ['/md/']); + } + + public function it_does_not_match_strings_that_do_match_specified_regex() + { + $this->shouldThrow()->duringNegativeMatch('match', 'everzet', ['/^ev.*et$/']); + } +} diff --git a/spec/PhpSpec/Matcher/StringStartMatcherSpec.php b/spec/PhpSpec/Matcher/StringStartMatcherSpec.php new file mode 100644 index 0000000..9b268c6 --- /dev/null +++ b/spec/PhpSpec/Matcher/StringStartMatcherSpec.php @@ -0,0 +1,55 @@ +presentString(Argument::type('string'))->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_supports_startWith_keyword_and_string_subject() + { + $this->supports('startWith', 'hello, everzet', ['hello'])->shouldReturn(true); + } + + public function it_does_not_support_anything_else() + { + $this->supports('startWith', [], [])->shouldReturn(false); + } + + public function it_matches_strings_that_start_with_specified_prefix() + { + $this->shouldNotThrow()->duringPositiveMatch('startWith', 'everzet', ['ev']); + } + + public function it_does_not_match_strings_that_do_not_start_with_specified_prefix() + { + $this->shouldThrow()->duringPositiveMatch('startWith', 'everzet', ['av']); + } + + public function it_matches_strings_that_do_not_start_with_specified_prefix() + { + $this->shouldNotThrow()->duringNegativeMatch('startWith', 'everzet', ['av']); + } + + public function it_does_not_match_strings_that_do_start_with_specified_prefix() + { + $this->shouldThrow()->duringNegativeMatch('startWith', 'everzet', ['ev']); + } +} diff --git a/spec/PhpSpec/Matcher/ThrowMatcherSpec.php b/spec/PhpSpec/Matcher/ThrowMatcherSpec.php new file mode 100644 index 0000000..4c11f33 --- /dev/null +++ b/spec/PhpSpec/Matcher/ThrowMatcherSpec.php @@ -0,0 +1,83 @@ +unwrapAll(Argument::any())->willReturnArgument(); + $presenter->presentValue(Argument::any())->willReturn('val1', 'val2'); + + $this->beConstructedWith($unwrapper, $presenter, $factory); + } + + public function it_supports_the_throw_alias_for_object_and_exception_name() + { + $this->supports('throw', '', [])->shouldReturn(true); + } + + public function it_accepts_a_method_during_which_an_exception_should_be_thrown(ArrayObject $arr) + { + $arr->ksort()->willThrow('\Exception'); + + $this->positiveMatch('throw', $arr, ['\Exception'])->during('ksort', []); + } + + public function it_accepts_a_method_during_which_an_error_specified_by_class_name_should_be_thrown(ArrayObject $arr) + { + $arr->ksort()->willThrow('\Error'); + + $this->positiveMatch('throw', $arr, ['\Error'])->during('ksort', []); + } + + public function it_accepts_a_method_during_which_an_error_specified_by_instance_should_be_thrown(ArrayObject $arr, ReflectionFactory $factory) + { + $error = new \Error(); + $arr->ksort()->will(function () use ($error) { + throw $error; + }); + $factory->create(Argument::any())->willReturn(new \ReflectionClass($error)); + + $this->positiveMatch('throw', $arr, [new \Error()])->during('ksort', []); + } + + public function it_accepts_a_method_during_which_an_exception_should_not_be_thrown(ArrayObject $arr) + { + $this->negativeMatch('throw', $arr, ['\Exception'])->during('ksort', []); + } + + public function it_accepts_a_method_during_which_an_error_should_not_be_thrown(ArrayObject $arr) + { + $this->negativeMatch('throw', $arr, ['\Error'])->during('ksort', []); + } + + public function it_throws_a_failure_exception_with_the_thrown_exceptions_message_if_a_positive_match_failed(Presenter $presenter) + { + $actually_thrown_error = new \Error('This is a test Error'); + + $callable = function () use ($actually_thrown_error) { + throw $actually_thrown_error; + }; + + $expected_error = new \PhpSpec\Exception\Example\FailureException( + 'Expected exception of class Exception, but got Error with the message: "This is a test Error"' + ); + + $incorrectly_matched_exception = new \Exception('This is the exception I expect to be thrown.'); + + $presenter->presentValue($actually_thrown_error)->willReturn('Error'); + $presenter->presentValue($incorrectly_matched_exception)->willReturn('Exception'); + + $this->shouldThrow($expected_error)->during('verifyPositive', [$callable, [], $incorrectly_matched_exception]); + } +} diff --git a/spec/PhpSpec/Matcher/TraversableContainMatcherSpec.php b/spec/PhpSpec/Matcher/TraversableContainMatcherSpec.php new file mode 100644 index 0000000..dfb22a0 --- /dev/null +++ b/spec/PhpSpec/Matcher/TraversableContainMatcherSpec.php @@ -0,0 +1,75 @@ +presentValue(Argument::any())->willReturn('traversable'); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_contain() + { + $this->supports('contain', $this->createGeneratorReturningValues([]), [''])->shouldReturn(true); + } + + public function it_positive_matches_generator_with_specified_value() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', ['contain', $this->createGeneratorReturningValues(['abc', 'def']), ['def']]) + ; + } + + public function it_does_not_positive_match_generator_without_specified_value() + { + $this + ->shouldThrow(FailureException::class) + ->during('positiveMatch', ['contain', $this->createGeneratorReturningValues(['def']), ['abc']]) + ; + } + + public function it_negative_matches_generator_without_specified_value() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', ['contain', $this->createGeneratorReturningValues(['abc']), ['def']]) + ; + } + + public function it_does_not_negative_matches_generator_with_specified_value() + { + $this + ->shouldThrow(FailureException::class) + ->during('negativeMatch', ['contain', $this->createGeneratorReturningValues(['abc', 'def']), ['def']]) + ; + } + + /** + * @param array $values + * + * @return \Generator + */ + private function createGeneratorReturningValues(array $values) + { + foreach ($values as $value) { + yield $value; + } + } +} diff --git a/spec/PhpSpec/Matcher/TraversableCountMatcherSpec.php b/spec/PhpSpec/Matcher/TraversableCountMatcherSpec.php new file mode 100644 index 0000000..3ca0864 --- /dev/null +++ b/spec/PhpSpec/Matcher/TraversableCountMatcherSpec.php @@ -0,0 +1,107 @@ +presentValue(Argument::any())->willReturn('traversable'); + $presenter->presentString(Argument::any())->willReturnArgument(); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_haveCount() + { + $this->supports('haveCount', $this->createGeneratorWithCount(0), [''])->shouldReturn(true); + } + + public function it_positive_matches_proper_generator_count() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', ['haveCount', $this->createGeneratorWithCount(2), [2]]) + ; + } + + public function it_does_not_positive_match_infinite_generator() + { + $this + ->shouldThrow(new FailureException('Expected traversable to have 10 items, but got more than that.')) + ->during('positiveMatch', ['haveCount', $this->createInifiteGenerator(), [10]]) + ; + } + + public function it_does_not_positive_match_wrong_generator_count() + { + $this + ->shouldThrow(new FailureException('Expected traversable to have 2 items, but got more than that.')) + ->during('positiveMatch', ['haveCount', $this->createGeneratorWithCount(3), [2]]) + ; + + $this + ->shouldThrow(new FailureException('Expected traversable to have 2 items, but got less than that.')) + ->during('positiveMatch', ['haveCount', $this->createGeneratorWithCount(1), [2]]) + ; + } + + public function it_negative_matches_wrong_generator_count() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', ['haveCount', $this->createGeneratorWithCount(3), [2]]) + ; + } + + public function it_negative_matches_infinite_generator() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', ['haveCount', $this->createInifiteGenerator(), [10]]) + ; + } + + public function it_does_not_negative_match_proper_generator_count() + { + $this + ->shouldThrow(new FailureException('Expected traversable not to have 2 items, but got it.')) + ->during('negativeMatch', ['haveCount', $this->createGeneratorWithCount(2), [2]]) + ; + } + + /** + * @param int $count + * + * @return \Generator + */ + private function createGeneratorWithCount($count) + { + for ($i = 0; $i < $count; ++$i) { + yield $i; + } + } + + /** + * @return \Generator + */ + private function createInifiteGenerator() + { + while (true) { + yield 42; + } + } +} diff --git a/spec/PhpSpec/Matcher/TraversableKeyMatcherSpec.php b/spec/PhpSpec/Matcher/TraversableKeyMatcherSpec.php new file mode 100644 index 0000000..d2ba2ae --- /dev/null +++ b/spec/PhpSpec/Matcher/TraversableKeyMatcherSpec.php @@ -0,0 +1,75 @@ +presentValue(Argument::any())->willReturn('traversable'); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_haveKey() + { + $this->supports('haveKey', $this->createGeneratorReturningKeys([]), [''])->shouldReturn(true); + } + + public function it_positive_matches_generator_with_specified_key() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', ['haveKey', $this->createGeneratorReturningKeys(['abc', 'def']), ['def']]) + ; + } + + public function it_does_not_positive_match_generator_without_specified_key() + { + $this + ->shouldThrow(FailureException::class) + ->during('positiveMatch', ['haveKey', $this->createGeneratorReturningKeys(['def']), ['abc']]) + ; + } + + public function it_negative_matches_generator_without_specified_key() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', ['haveKey', $this->createGeneratorReturningKeys(['abc']), ['def']]) + ; + } + + public function it_does_not_negative_matches_generator_with_specified_key() + { + $this + ->shouldThrow(FailureException::class) + ->during('negativeMatch', ['haveKey', $this->createGeneratorReturningKeys(['abc', 'def']), ['def']]) + ; + } + + /** + * @param array $keys + * + * @return \Generator + */ + private function createGeneratorReturningKeys(array $keys) + { + foreach ($keys as $key) { + yield $key => 'value'; + } + } +} diff --git a/spec/PhpSpec/Matcher/TraversableKeyValueMatcherSpec.php b/spec/PhpSpec/Matcher/TraversableKeyValueMatcherSpec.php new file mode 100644 index 0000000..31a88ee --- /dev/null +++ b/spec/PhpSpec/Matcher/TraversableKeyValueMatcherSpec.php @@ -0,0 +1,85 @@ +presentValue(Argument::any())->willReturn('traversable'); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf(Matcher::class); + } + + public function it_responds_to_haveKeyWithValue() + { + $this->supports('haveKeyWithValue', $this->createGeneratorReturningArray([]), ['', ''])->shouldReturn(true); + } + + public function it_positive_matches_generator_with_specified_key_and_value() + { + $this + ->shouldNotThrow() + ->during('positiveMatch', ['haveKeyWithValue', $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['a', 'b']]) + ; + } + + public function it_does_not_positive_match_generator_without_specified_key_and_value() + { + $this + ->shouldThrow(FailureException::class) + ->during('positiveMatch', ['haveKeyWithValue', $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['a', 'c']]) + ; + + $this + ->shouldThrow(FailureException::class) + ->during('positiveMatch', ['haveKeyWithValue', $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['b', 'd']]) + ; + + $this + ->shouldThrow(FailureException::class) + ->during('positiveMatch', ['haveKeyWithValue', $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['b', 'a']]) + ; + } + + public function it_negative_matches_generator_without_specified_key_and_value() + { + $this + ->shouldNotThrow() + ->during('negativeMatch', ['haveKeyWithValue', $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['a', 'c']]) + ; + } + + public function it_does_not_negative_matches_generator_with_specified_key_and_value() + { + $this + ->shouldThrow(FailureException::class) + ->during('negativeMatch', ['haveKeyWithValue', $this->createGeneratorReturningArray(['a' => 'b', 'c' => 'd']), ['a', 'b']]) + ; + } + + /** + * @param array $array + * + * @return \Generator + */ + private function createGeneratorReturningArray(array $array) + { + foreach ($array as $key => $value) { + yield $key => $value; + } + } +} diff --git a/spec/PhpSpec/Matcher/TriggerMatcherSpec.php b/spec/PhpSpec/Matcher/TriggerMatcherSpec.php new file mode 100644 index 0000000..a50a059 --- /dev/null +++ b/spec/PhpSpec/Matcher/TriggerMatcherSpec.php @@ -0,0 +1,53 @@ +unwrapAll(Argument::any())->willReturnArgument(); + + $this->beConstructedWith($unwrapper); + } + + public function it_supports_the_trigger_alias_for_object_and_exception_name() + { + $this->supports('trigger', '', [])->shouldReturn(true); + } + + public function it_accepts_a_method_during_which_an_error_should_be_triggered(ArrayObject $arr) + { + $arr->ksort()->will(function () { + trigger_error('An error', E_USER_NOTICE); + }); + + $this->positiveMatch('trigger', $arr, [E_USER_NOTICE, 'An error'])->during('ksort', []); + } + + public function it_accepts_a_method_during_which_any_error_should_be_triggered(ArrayObject $arr) + { + $arr->ksort()->will(function () { + trigger_error('An error', E_USER_NOTICE); + }); + + $this->positiveMatch('trigger', $arr, [null, null])->during('ksort', []); + } + + public function it_accepts_a_method_during_which_an_error_should_not_be_triggered(ArrayObject $arr) + { + $this->negativeMatch('trigger', $arr, [E_USER_NOTICE, 'An error'])->during('ksort', []); + } + + public function it_accepts_a_method_during_which_any_error_should_not_be_triggered(ArrayObject $arr) + { + $this->negativeMatch('trigger', $arr, [null, null])->during('ksort', []); + } +} diff --git a/spec/PhpSpec/Matcher/TypeMatcherSpec.php b/spec/PhpSpec/Matcher/TypeMatcherSpec.php new file mode 100644 index 0000000..1f3fc5d --- /dev/null +++ b/spec/PhpSpec/Matcher/TypeMatcherSpec.php @@ -0,0 +1,81 @@ +presentString(Argument::any())->willReturnArgument(); + $presenter->presentValue(Argument::any())->willReturn('object'); + + $this->beConstructedWith($presenter); + } + + public function it_is_a_matcher() + { + $this->shouldBeAnInstanceOf('PhpSpec\Matcher\Matcher'); + } + + public function it_responds_to_beAnInstanceOf() + { + $this->supports('beAnInstanceOf', '', [''])->shouldReturn(true); + } + + public function it_responds_to_returnAnInstanceOf() + { + $this->supports('returnAnInstanceOf', '', [''])->shouldReturn(true); + } + + public function it_responds_to_haveType() + { + $this->supports('haveType', '', [''])->shouldReturn(true); + } + + public function it_matches_subclass_instance(ArrayObject $object) + { + $this->shouldNotThrow() + ->duringPositiveMatch('haveType', $object, ['ArrayObject']); + } + + public function it_matches_interface_instance(ArrayObject $object) + { + $this->shouldNotThrow() + ->duringPositiveMatch('haveType', $object, ['ArrayAccess']); + } + + public function it_does_not_match_wrong_class(ArrayObject $object) + { + $this->shouldThrow(new FailureException( + 'Expected an instance of stdClass, but got object.' + ))->duringPositiveMatch('haveType', $object, ['stdClass']); + } + + public function it_does_not_match_wrong_interface(ArrayObject $object) + { + $this->shouldThrow(new FailureException( + 'Expected an instance of SessionHandlerInterface, but got object.' + ))->duringPositiveMatch('haveType', $object, ['SessionHandlerInterface']); + } + + public function it_matches_other_class(ArrayObject $object) + { + $this->shouldNotThrow()->duringNegativeMatch('haveType', $object, ['stdClass']); + } + + public function it_matches_other_interface() + { + $this->shouldNotThrow() + ->duringNegativeMatch('haveType', $this, ['SessionHandlerInterface']); + } +} diff --git a/spec/PhpSpec/Message/CurrentExampleTrackerSpec.php b/spec/PhpSpec/Message/CurrentExampleTrackerSpec.php new file mode 100644 index 0000000..8914711 --- /dev/null +++ b/spec/PhpSpec/Message/CurrentExampleTrackerSpec.php @@ -0,0 +1,26 @@ +shouldHaveType('PhpSpec\Message\CurrentExampleTracker'); + } + + public function it_should_set_a_message() + { + $this->setCurrentExample('test'); + $this->getCurrentExample()->shouldBe('test'); + } + + public function it_should_be_null_on_construction() + { + $this->getCurrentExample()->shouldBe(null); + } +} diff --git a/spec/PhpSpec/NamespaceProvider/ComposerPsrNamespaceProviderSpec.php b/spec/PhpSpec/NamespaceProvider/ComposerPsrNamespaceProviderSpec.php new file mode 100644 index 0000000..143654f --- /dev/null +++ b/spec/PhpSpec/NamespaceProvider/ComposerPsrNamespaceProviderSpec.php @@ -0,0 +1,53 @@ +beConstructedWith(__DIR__.'/../../..', 'spec'); + } + + public function it_is_initializable() + { + $this->shouldHaveType(ComposerPsrNamespaceProvider::class); + } + + public function it_should_return_a_map_of_locations() + { + $this->getNamespaces()->shouldHaveKey('Proget\PHPStan\PhpSpec\\'); + $this->getNamespaces()->shouldHaveNamespaceLocation( + 'Proget\PHPStan\PhpSpec\\', + 'src', + NamespaceProvider::AUTOLOADING_STANDARD_PSR4 + ); + } + + public function getMatchers(): array + { + return [ + 'haveNamespaceLocation' => function ($subject, $namespace, $location, $standard) { + $expectedNamespaceLocation = new NamespaceLocation( + $namespace, + $location, + $standard + ); + foreach ($subject as $namespaceLocation) { + if ($namespaceLocation == $expectedNamespaceLocation) { + return true; + } + } + + return false; + } + ]; + } +} diff --git a/spec/PhpSpec/Process/Context/JsonExecutionContextSpec.php b/spec/PhpSpec/Process/Context/JsonExecutionContextSpec.php new file mode 100644 index 0000000..fb35ccd --- /dev/null +++ b/spec/PhpSpec/Process/Context/JsonExecutionContextSpec.php @@ -0,0 +1,39 @@ +beConstructedThrough('fromEnv', [['PHPSPEC_EXECUTION_CONTEXT' => '{"generated-types":[]}']]); + } + + public function it_is_an_execution_context() + { + $this->shouldHaveType('PhpSpec\Process\Context\ExecutionContext'); + } + + public function it_contains_no_generated_classes_when_created() + { + $this->getGeneratedTypes()->shouldReturn([]); + } + + public function it_remembers_what_classes_were_generated() + { + $this->addGeneratedType('PhpSpec\Foo'); + + $this->getGeneratedTypes()->shouldReturn(['PhpSpec\Foo']); + } + + public function it_can_be_serialized_as_env_array() + { + $this->addGeneratedType('PhpSpec\Foo'); + + $this->asEnv()->shouldReturn(['PHPSPEC_EXECUTION_CONTEXT' => '{"generated-types":["PhpSpec\\\\Foo"]}']); + } +} diff --git a/spec/PhpSpec/Process/Prerequisites/SuitePrerequisitesSpec.php b/spec/PhpSpec/Process/Prerequisites/SuitePrerequisitesSpec.php new file mode 100644 index 0000000..d81095c --- /dev/null +++ b/spec/PhpSpec/Process/Prerequisites/SuitePrerequisitesSpec.php @@ -0,0 +1,30 @@ +beConstructedWith($executionContext); + } + + public function it_does_nothing_when_types_exist(ExecutionContext $executionContext) + { + $executionContext->getGeneratedTypes()->willReturn(['stdClass']); + + $this->guardPrerequisites(); + } + + public function it_throws_execption_when_types_do_not_exist(ExecutionContext $executionContext) + { + $executionContext->getGeneratedTypes()->willReturn(['stdClassXXX']); + + $this->shouldThrow('PhpSpec\Process\Prerequisites\PrerequisiteFailedException')->duringGuardPrerequisites(); + } +} diff --git a/spec/PhpSpec/Process/ReRunner/CompositeReRunnerSpec.php b/spec/PhpSpec/Process/ReRunner/CompositeReRunnerSpec.php new file mode 100644 index 0000000..7c2820c --- /dev/null +++ b/spec/PhpSpec/Process/ReRunner/CompositeReRunnerSpec.php @@ -0,0 +1,61 @@ +isSupported()->willReturn(false); + $reRunner2->isSupported()->willReturn(false); + + $this->beConstructedWith( + [ + $reRunner1->getWrappedObject(), + $reRunner2->getWrappedObject() + ] + ); + } + + public function it_is_a_rerunner() + { + $this->shouldHaveType('PhpSpec\Process\ReRunner'); + } + + public function it_invokes_the_first_supported_child_to_rerun_the_suite_even_if_later_children_are_supported( + PlatformSpecificReRunner $reRunner1, + PlatformSpecificReRunner $reRunner2 + ) { + $reRunner1->isSupported()->willReturn(true); + $reRunner2->isSupported()->willReturn(true); + + $reRunner1->reRunSuite()->shouldBeCalled(); + + $this->reRunSuite(); + + $reRunner1->reRunSuite()->shouldHaveBeenCalled(); + $reRunner2->reRunSuite()->shouldNotHaveBeenCalled(); + } + + public function it_skips_early_child_if_it_is_not_supported_and_invokes_runsuite_on_later_supported_child( + PlatformSpecificReRunner $reRunner1, + PlatformSpecificReRunner $reRunner2 + ) { + $reRunner1->isSupported()->willReturn(false); + $reRunner2->isSupported()->willReturn(true); + + $reRunner2->reRunSuite()->should(function () { + }); + ; + + $this->reRunSuite(); + + $reRunner1->reRunSuite()->shouldNotHaveBeenCalled(); + $reRunner2->reRunSuite()->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Process/ReRunner/OptionalReRunnerSpec.php b/spec/PhpSpec/Process/ReRunner/OptionalReRunnerSpec.php new file mode 100644 index 0000000..0885bfa --- /dev/null +++ b/spec/PhpSpec/Process/ReRunner/OptionalReRunnerSpec.php @@ -0,0 +1,35 @@ +beconstructedWith($decoratedReRunner, $io); + } + + public function it_reruns_the_suite_if_it_is_enabled_in_the_config(ConsoleIO $io, ReRunner $decoratedReRunner) + { + $io->isRerunEnabled()->willReturn(true); + + $this->reRunSuite(); + + $decoratedReRunner->reRunSuite()->shouldHaveBeenCalled(); + } + + public function it_does_not_rerun_the_suite_if_it_is_disabled_in_the_config(ConsoleIO $io, ReRunner $decoratedReRunner) + { + $io->isRerunEnabled()->willReturn(false); + + $this->reRunSuite(); + + $decoratedReRunner->reRunSuite()->shouldNotHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Process/ReRunner/PcntlReRunnerSpec.php b/spec/PhpSpec/Process/ReRunner/PcntlReRunnerSpec.php new file mode 100644 index 0000000..1906d73 --- /dev/null +++ b/spec/PhpSpec/Process/ReRunner/PcntlReRunnerSpec.php @@ -0,0 +1,29 @@ +beConstructedThrough('withExecutionContext', [$executableFinder, $executionContext]); + } + + public function it_is_a_rerunner() + { + $this->shouldHaveType('PhpSpec\Process\ReRunner'); + } + + public function it_is_not_supported_when_php_process_is_not_found(PhpExecutableFinder $executableFinder) + { + $executableFinder->find()->willReturn(false); + + $this->isSupported()->shouldReturn(false); + } +} diff --git a/spec/PhpSpec/Process/ReRunner/ProcOpenReRunnerSpec.php b/spec/PhpSpec/Process/ReRunner/ProcOpenReRunnerSpec.php new file mode 100644 index 0000000..c489050 --- /dev/null +++ b/spec/PhpSpec/Process/ReRunner/ProcOpenReRunnerSpec.php @@ -0,0 +1,40 @@ + + * (c) Konstantin Kudryashov + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace spec\PhpSpec\Process\ReRunner; + +use PhpSpec\ObjectBehavior; +use PhpSpec\Process\Context\ExecutionContext; +use Symfony\Component\Process\PhpExecutableFinder; + +class ProcOpenReRunnerSpec extends ObjectBehavior +{ + public function let(PhpExecutableFinder $executableFinder, ExecutionContext $executionContext) + { + $this->beConstructedThrough('withExecutionContext', [$executableFinder, $executionContext]); + } + + public function it_is_a_rerunner() + { + $this->shouldHaveType('PhpSpec\Process\ReRunner'); + } + + public function it_is_not_supported_when_php_process_is_not_found(PhpExecutableFinder $executableFinder) + { + $executableFinder->find()->willReturn(false); + + $this->isSupported()->shouldReturn(false); + } +} diff --git a/spec/PhpSpec/Process/ReRunner/WindowsPassthruReRunnerSpec.php b/spec/PhpSpec/Process/ReRunner/WindowsPassthruReRunnerSpec.php new file mode 100644 index 0000000..51dd15b --- /dev/null +++ b/spec/PhpSpec/Process/ReRunner/WindowsPassthruReRunnerSpec.php @@ -0,0 +1,40 @@ + + * (c) Konstantin Kudryashov + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace spec\PhpSpec\Process\ReRunner; + +use PhpSpec\ObjectBehavior; +use PhpSpec\Process\Context\ExecutionContext; +use Symfony\Component\Process\PhpExecutableFinder; + +class WindowsPassthruReRunnerSpec extends ObjectBehavior +{ + public function let(PhpExecutableFinder $executableFinder, ExecutionContext $executionContext) + { + $this->beConstructedThrough('withExecutionContext', [$executableFinder, $executionContext]); + } + + public function it_is_a_rerunner() + { + $this->shouldHaveType('PhpSpec\Process\ReRunner'); + } + + public function it_is_not_supported_when_php_process_is_not_found(PhpExecutableFinder $executableFinder) + { + $executableFinder->find()->willReturn(false); + + $this->isSupported()->shouldReturn(false); + } +} diff --git a/spec/PhpSpec/Process/Shutdown/ShutdownSpec.php b/spec/PhpSpec/Process/Shutdown/ShutdownSpec.php new file mode 100644 index 0000000..b3b6915 --- /dev/null +++ b/spec/PhpSpec/Process/Shutdown/ShutdownSpec.php @@ -0,0 +1,23 @@ +beAnInstanceOf('PhpSpec/Process/Shutdown/Shutdown'); + } + + public function it_runs_through_all_registered_actions(ShutdownAction $action) + { + $action->runAction(null)->shouldBeCalled(); + $this->registerAction($action); + $this->runShutdown(); + } +} diff --git a/spec/PhpSpec/Process/Shutdown/UpdateConsoleActionSpec.php b/spec/PhpSpec/Process/Shutdown/UpdateConsoleActionSpec.php new file mode 100644 index 0000000..51ac92a --- /dev/null +++ b/spec/PhpSpec/Process/Shutdown/UpdateConsoleActionSpec.php @@ -0,0 +1,27 @@ +beConstructedWith($currentExample, $currentExampleWriter); + } + + public function it_should_update_the_console(FatalPresenter $currentExampleWriter) + { + $currentExample = new CurrentExampleTracker(); + $error = ['type' => 1, 'message' => 'Hello']; + $currentExample->getCurrentExample('Hello'); + $currentExampleWriter->displayFatal($currentExample, $error)->shouldBeCalled(); + $this->runAction($error); + } +} diff --git a/spec/PhpSpec/Runner/CollaboratorManagerSpec.php b/spec/PhpSpec/Runner/CollaboratorManagerSpec.php new file mode 100644 index 0000000..a9f3b27 --- /dev/null +++ b/spec/PhpSpec/Runner/CollaboratorManagerSpec.php @@ -0,0 +1,74 @@ +beConstructedWith($presenter); + $presenter->presentString(Argument::cetera())->willReturn('someString'); + } + + public function it_stores_collaborators_by_name($collaborator) + { + $this->set('custom_collaborator', $collaborator); + $this->get('custom_collaborator')->shouldReturn($collaborator); + } + + public function it_provides_a_method_to_check_if_collaborator_exists($collaborator) + { + $this->set('custom_collaborator', $collaborator); + + $this->has('custom_collaborator')->shouldReturn(true); + $this->has('nonexistent')->shouldReturn(false); + } + + public function it_throws_CollaboratorException_on_attempt_to_get_unexisting_collaborator() + { + $this->shouldThrow('PhpSpec\Exception\Wrapper\CollaboratorException') + ->duringGet('nonexistent'); + } + + public function it_creates_function_arguments_for_ReflectionFunction( + ReflectionFunction $function, + ReflectionParameter $param1, + ReflectionParameter $param2 + ) { + $this->set('arg1', '123'); + $this->set('arg2', '456'); + $this->set('arg3', '789'); + + $function->getParameters()->willReturn([$param1, $param2]); + $param1->getName()->willReturn('arg1'); + $param2->getName()->willReturn('arg3'); + + $this->getArgumentsFor($function)->shouldReturn(['123', '789']); + } + + public function it_creates_null_function_arguments_for_ReflectionFunction_if_no_collaborator_found( + ReflectionFunction $function, + ReflectionParameter $param1, + ReflectionParameter $param2 + ) { + $this->set('arg1', '123'); + $this->set('arg2', '456'); + $this->set('arg3', '789'); + + $function->getParameters()->willReturn([$param1, $param2]); + $param1->getName()->willReturn('arg4'); + $param2->getName()->willReturn('arg3'); + + $this->getArgumentsFor($function)->shouldReturn([null, '789']); + } +} diff --git a/spec/PhpSpec/Runner/ExampleRunnerSpec.php b/spec/PhpSpec/Runner/ExampleRunnerSpec.php new file mode 100644 index 0000000..7d8ba98 --- /dev/null +++ b/spec/PhpSpec/Runner/ExampleRunnerSpec.php @@ -0,0 +1,175 @@ +beConstructedWith($dispatcher, $presenter); + + $example->getSpecification()->willReturn($specification); + $example->getFunctionReflection()->willReturn($exampReflection); + $specification->getClassReflection()->willReturn($specReflection); + $specReflection->newInstance()->willReturn($context); + } + + public function it_executes_example_in_newly_created_context( + ExampleNode $example, + ReflectionMethod $exampReflection, + Specification $context + ) { + $example->isPending()->willReturn(false); + + $exampReflection->getParameters()->willReturn([]); + $exampReflection->invokeArgs($context, [])->shouldBeCalled(); + + $this->run($example); + } + + public function it_dispatches_ExampleEvent_with_pending_status_if_example_is_pending( + EventDispatcherInterface $dispatcher, + ExampleNode $example + ) { + $example->isPending()->willReturn(true); + + $dispatcher->dispatch('beforeExample', Argument::any())->shouldBeCalled(); + $dispatcher->dispatch( + 'afterExample', + Argument::which('getResult', ExampleEvent::PENDING) + )->shouldBeCalled(); + + $this->run($example); + } + + public function it_dispatches_ExampleEvent_with_failed_status_if_matcher_throws_exception( + EventDispatcherInterface $dispatcher, + ExampleNode $example, + ReflectionMethod $exampReflection, + Specification $context + ) { + $example->isPending()->willReturn(false); + + $exampReflection->getParameters()->willReturn([]); + $exampReflection->invokeArgs($context, []) + ->willThrow('PhpSpec\Exception\Example\FailureException'); + + $dispatcher->dispatch('beforeExample', Argument::any())->shouldBeCalled(); + $dispatcher->dispatch( + 'afterExample', + Argument::which('getResult', ExampleEvent::FAILED) + )->shouldBeCalled(); + + $this->run($example); + } + + public function it_dispatches_ExampleEvent_with_failed_status_if_example_throws_exception( + EventDispatcherInterface $dispatcher, + ExampleNode $example, + ReflectionMethod $exampReflection, + Specification $context + ) { + $example->isPending()->willReturn(false); + + $exampReflection->getParameters()->willReturn([]); + $exampReflection->invokeArgs($context, [])->willThrow('RuntimeException'); + + $dispatcher->dispatch('beforeExample', Argument::any())->shouldBeCalled(); + $dispatcher->dispatch( + 'afterExample', + Argument::which('getResult', ExampleEvent::BROKEN) + )->shouldBeCalled(); + + $this->run($example); + } + + public function it_dispatches_ExampleEvent_with_failed_status_if_example_throws_an_error( + EventDispatcherInterface $dispatcher, + ExampleNode $example, + ReflectionMethod $exampReflection, + Specification $context + ) { + $example->isPending()->willReturn(false); + + $exampReflection->getParameters()->willReturn([]); + $exampReflection->invokeArgs($context, [])->willThrow('Error'); + + $dispatcher->dispatch('beforeExample', Argument::any())->shouldBeCalled(); + $dispatcher->dispatch( + 'afterExample', + Argument::which('getResult', ExampleEvent::BROKEN) + )->shouldBeCalled(); + + $this->run($example); + } + + public function it_runs_all_supported_maintainers_before_and_after_each_example( + ExampleNode $example, + ReflectionMethod $exampReflection, + Maintainer $maintainer + ) { + $example->isPending()->willReturn(false); + + $exampReflection->getParameters()->willReturn([]); + $exampReflection->invokeArgs(Argument::cetera())->willReturn(null); + + $maintainer->getPriority()->willReturn(1); + $maintainer->supports($example)->willReturn(true); + + $maintainer->prepare($example, Argument::cetera())->shouldBeCalled(); + $maintainer->teardown($example, Argument::cetera())->shouldBeCalled(); + + $this->registerMaintainer($maintainer); + $this->run($example); + } + + public function it_runs_let_and_letgo_maintainer_before_and_after_each_example_if_the_example_throws_an_exception( + ExampleNode $example, + SpecificationNode $specification, + ReflectionClass $specReflection, + ReflectionMethod $exampReflection, + LetAndLetgoMaintainer $maintainer, + Specification $context + ) { + $example->isPending()->willReturn(false); + $example->getFunctionReflection()->willReturn($exampReflection); + $example->getSpecification()->willReturn($specification); + $specification->getClassReflection()->willReturn($specReflection); + $specReflection->newInstanceArgs()->willReturn($context); + + $exampReflection->getParameters()->willReturn([]); + $exampReflection->invokeArgs($context, [])->willThrow('RuntimeException'); + + $maintainer->getPriority()->willReturn(1); + $maintainer->supports($example)->willReturn(true); + + $maintainer->prepare($example, Argument::cetera())->shouldBeCalled(); + $maintainer->teardown($example, Argument::cetera())->shouldBeCalled(); + + $this->registerMaintainer($maintainer); + $this->run($example); + } +} diff --git a/spec/PhpSpec/Runner/Maintainer/ErrorMaintainerSpec.php b/spec/PhpSpec/Runner/Maintainer/ErrorMaintainerSpec.php new file mode 100644 index 0000000..3338b95 --- /dev/null +++ b/spec/PhpSpec/Runner/Maintainer/ErrorMaintainerSpec.php @@ -0,0 +1,41 @@ +beConstructedWith(0); + } + + public function it_is_initializable() + { + $this->shouldHaveType(ErrorMaintainer::class); + } + + public function it_return_false_when_error_suppresed_or_no_error_reporting() + { + $oldLevel = error_reporting(0); + $this->errorHandler(0, 'error message', 'file', 1)->shouldBe(false); + error_reporting($oldLevel); + } + + public function it_return_true_when_recoverable_level_and_message_match() + { + $msg = 'Argument 1 passed to '.self::class.'::test() must be an instance of string, string given'; + $this->errorHandler(E_RECOVERABLE_ERROR, $msg, 'file', 1)->shouldBe(true); + } + + public function it_throws_error_exception_when_message_not_match() + { + $this->shouldThrow(ExampleException\ErrorException::class) + ->during('errorHandler', [0, 'error message', 'file', 1]); + } +} diff --git a/spec/PhpSpec/Runner/Maintainer/MatchersMaintainerSpec.php b/spec/PhpSpec/Runner/Maintainer/MatchersMaintainerSpec.php new file mode 100644 index 0000000..3711fe2 --- /dev/null +++ b/spec/PhpSpec/Runner/Maintainer/MatchersMaintainerSpec.php @@ -0,0 +1,30 @@ +beConstructedWith($presenter, [$matcher]); + $this->prepare($example, $context, $matchers, $collaborators); + + $matchers->replace([$matcher])->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/Runner/MatcherManagerSpec.php b/spec/PhpSpec/Runner/MatcherManagerSpec.php new file mode 100644 index 0000000..33ac786 --- /dev/null +++ b/spec/PhpSpec/Runner/MatcherManagerSpec.php @@ -0,0 +1,51 @@ +beConstructedWith($presenter); + $presenter->presentString(Argument::cetera())->willReturn('some strong'); + $presenter->presentValue(Argument::cetera())->willReturn('some value'); + } + + public function it_searches_in_registered_matchers(Matcher $matcher) + { + $matcher->getPriority()->willReturn(0); + $matcher->supports('startWith', 'hello, world', ['hello'])->willReturn(true); + + $this->add($matcher); + $this->find('startWith', 'hello, world', ['hello'])->shouldReturn($matcher); + } + + public function it_searches_matchers_by_their_priority( + Matcher $matcher1, + Matcher $matcher2 + ) { + $matcher1->getPriority()->willReturn(2); + $matcher1->supports('startWith', 'hello, world', ['hello'])->willReturn(true); + $matcher2->getPriority()->willReturn(5); + $matcher2->supports('startWith', 'hello, world', ['hello'])->willReturn(true); + + $this->add($matcher1); + $this->add($matcher2); + + $this->find('startWith', 'hello, world', ['hello'])->shouldReturn($matcher2); + } + + public function it_throws_MatcherNotFoundException_if_matcher_not_found() + { + $this->shouldThrow('PhpSpec\Exception\Wrapper\MatcherNotFoundException') + ->duringFind('startWith', 'hello, world', ['hello']); + } +} diff --git a/spec/PhpSpec/Runner/SpecificationRunnerSpec.php b/spec/PhpSpec/Runner/SpecificationRunnerSpec.php new file mode 100644 index 0000000..b42ec95 --- /dev/null +++ b/spec/PhpSpec/Runner/SpecificationRunnerSpec.php @@ -0,0 +1,76 @@ +beConstructedWith($dispatcher, $exampleRunner); + } + + public function it_passes_each_specification_example_to_ExampleRunner( + SpecificationNode $specification, + ExampleNode $ex1, + ExampleNode $ex2, + ExampleRunner $exampleRunner + ) { + $specification->getExamples()->willReturn([$ex1, $ex2]); + + $exampleRunner->run($ex1)->shouldBeCalled(); + $exampleRunner->run($ex2)->shouldBeCalled(); + + $this->run($specification); + } + + public function it_returns_examples_max_resultCode( + SpecificationNode $specification, + ExampleNode $ex1, + ExampleNode $ex2, + ExampleRunner $exampleRunner + ) { + $specification->getExamples()->willReturn([$ex1, $ex2]); + + $exampleRunner->run($ex1)->willReturn(2); + $exampleRunner->run($ex2)->willReturn(0); + + $this->run($specification)->shouldReturn(2); + } + + public function it_returns_0_resultCode_if_no_examples_found(SpecificationNode $specification) + { + $specification->getExamples()->willReturn([]); + + $this->run($specification)->shouldReturn(0); + } + + public function it_dispatches_SpecificationEvent_before_and_after_examples_run( + EventDispatcherInterface $dispatcher, + SpecificationNode $specification + ) { + $specification->getExamples()->willReturn([]); + + $dispatcher->dispatch( + 'beforeSpecification', + Argument::type('PhpSpec\Event\SpecificationEvent') + )->shouldBeCalled(); + + $dispatcher->dispatch( + 'afterSpecification', + Argument::type('PhpSpec\Event\SpecificationEvent') + )->shouldBeCalled(); + + $this->run($specification); + } +} diff --git a/spec/PhpSpec/Runner/SuiteRunnerSpec.php b/spec/PhpSpec/Runner/SuiteRunnerSpec.php new file mode 100644 index 0000000..202d67a --- /dev/null +++ b/spec/PhpSpec/Runner/SuiteRunnerSpec.php @@ -0,0 +1,113 @@ +beConstructedWith($dispatcher, $specRunner); + $suite->getSpecifications()->willReturn([$spec1, $spec2]); + } + + public function it_runs_all_specs_in_the_suite_through_the_specrunner($suite, $specRunner, $spec1, $spec2) + { + $specRunner->run($spec1)->willReturn(ExampleEvent::PASSED); + $specRunner->run($spec2)->willReturn(ExampleEvent::PASSED); + $this->run($suite); + + $specRunner->run($spec1)->shouldHaveBeenCalled(); + $specRunner->run($spec2)->shouldHaveBeenCalled(); + } + + public function it_stops_running_subsequent_specs_when_a_spec_throws_a_StopOnFailureException($suite, $specRunner, $spec1, $spec2) + { + $specRunner->run($spec1)->willThrow(new StopOnFailureException()); + + $this->run($suite); + + $specRunner->run($spec2)->shouldNotBeenCalled(); + } + + public function it_returns_a_successful_result_when_all_specs_in_suite_pass($suite, $specRunner, $spec1, $spec2) + { + $specRunner->run($spec1)->willReturn(ExampleEvent::PASSED); + $specRunner->run($spec2)->willReturn(ExampleEvent::PASSED); + + $this->run($suite)->shouldReturn(ExampleEvent::PASSED); + } + + public function it_returns_a_broken_result_when_one_spec_is_broken($suite, $specRunner, $spec1, $spec2) + { + $specRunner->run($spec1)->willReturn(ExampleEvent::FAILED); + $specRunner->run($spec2)->willReturn(ExampleEvent::BROKEN); + + $this->run($suite)->shouldReturn(ExampleEvent::BROKEN); + } + + public function it_returns_a_failed_result_when_one_spec_failed($suite, $specRunner, $spec1, $spec2) + { + $specRunner->run($spec1)->willReturn(ExampleEvent::FAILED); + $specRunner->run($spec2)->willReturn(ExampleEvent::PENDING); + + $this->run($suite)->shouldReturn(ExampleEvent::FAILED); + } + + public function it_dispatches_events_before_and_after_the_suite($suite, $specRunner, $spec1, $spec2, $dispatcher) + { + $specRunner->run($spec1)->willReturn(ExampleEvent::PASSED); + $specRunner->run($spec2)->willReturn(ExampleEvent::PASSED); + + $this->run($suite); + + $dispatcher->dispatch( + 'beforeSuite', + Argument::type('PhpSpec\Event\SuiteEvent') + )->shouldHaveBeenCalled(); + + $dispatcher->dispatch( + 'afterSuite', + Argument::type('PhpSpec\Event\SuiteEvent') + )->shouldHaveBeenCalled(); + } + + public function it_dispatches_afterSuite_event_with_result_and_time($suite, $specRunner, $dispatcher) + { + $specRunner->run(Argument::any())->will(function () { + // Wait a few microseconds to ensure that the spec passes even on fast machines + usleep(10); + + return ExampleEvent::FAILED; + }); + + $this->run($suite); + + $dispatcher->dispatch( + 'afterSuite', + Argument::that( + function ($event) { + return ($event->getTime() > 0) + && ($event->getResult() == ExampleEvent::FAILED); + } + ) + )->shouldHaveBeenCalled(); + } +} diff --git a/spec/PhpSpec/ServiceContainer/IndexedServiceContainerSpec.php b/spec/PhpSpec/ServiceContainer/IndexedServiceContainerSpec.php new file mode 100644 index 0000000..4bcf5e4 --- /dev/null +++ b/spec/PhpSpec/ServiceContainer/IndexedServiceContainerSpec.php @@ -0,0 +1,121 @@ +setParam('some_param', 42); + $this->getParam('some_param')->shouldReturn(42); + } + + public function it_returns_null_value_for_unexisting_parameter() + { + $this->getParam('unexisting')->shouldReturn(null); + } + + public function it_returns_custom_default_for_unexisting_parameter_if_provided() + { + $this->getParam('unexisting', 42)->shouldReturn(42); + } + + public function it_stores_services($service) + { + $this->set('some_service', $service); + $this->get('some_service')->shouldReturn($service); + } + + public function it_knows_when_services_are_not_defined() + { + $this->has('some_service')->shouldReturn(false); + } + + public function it_knows_when_services_are_defined($service) + { + $this->set('some_service', $service); + $this->has('some_service')->shouldReturn(true); + } + + public function it_returns_nothing_when_no_services_are_tagged() + { + $this->getByTag('some_tag')->shouldReturn([]); + } + + public function it_returns_services_which_are_set_using_tags($service) + { + $obj = new \stdClass(); + $this->set('some_service', $obj, ['some_tag']); + $this->getByTag('some_tag')->shouldReturn([$obj]); + } + + public function it_returns_services_which_are_defined_using_tags() + { + $obj = new \stdClass(); + $this->define('some_service', function () use ($obj) { + return $obj; + }, ['some_tag']); + $this->getByTag('some_tag')->shouldReturn([$obj]); + } + + public function it_throws_exception_when_trying_to_get_unexisting_service() + { + $this->shouldThrow('InvalidArgumentException')->duringGet('unexisting'); + } + + public function it_evaluates_factory_function_only_once_for_shared_services() + { + $this->define('random_number', function () { + return rand(); + }); + $number1 = $this->get('random_number'); + $number2 = $this->get('random_number'); + + $number2->shouldBe($number1); + } + + public function it_uses_new_definition_when_a_service_is_redefined() + { + $this->define('some_service', function () { + return 1; + }); + $this->get('some_service'); + + + $this->define('some_service', function () { + return 2; + }); + + $this->get('some_service')->shouldBe(2); + } + + public function it_does_not_evaluate_callables_that_are_set() + { + $this->set('some_service', function () { + return 100; + }); + $this->get('some_service')->shouldNotBe(100); + } + + public function it_provides_a_way_to_remove_service_by_key($service) + { + $this->set('collection1.some_service', $service); + $this->remove('collection1.some_service'); + + $this->shouldThrow()->duringGet('collection1.some_service'); + } + + public function it_supports_custom_service_configurators() + { + $this->addConfigurator(function ($c) { + $c->setParam('name', 'Jim'); + }); + $this->configure(); + + $this->getParam('name')->shouldReturn('Jim'); + } +} diff --git a/spec/PhpSpec/Specification/ErrorSpecificationSpec.php b/spec/PhpSpec/Specification/ErrorSpecificationSpec.php new file mode 100644 index 0000000..ae5ca20 --- /dev/null +++ b/spec/PhpSpec/Specification/ErrorSpecificationSpec.php @@ -0,0 +1,16 @@ +shouldHaveType(Specification::class); + } +} diff --git a/spec/PhpSpec/Util/ClassFileAnalyserSpec.php b/spec/PhpSpec/Util/ClassFileAnalyserSpec.php new file mode 100644 index 0000000..2062d47 --- /dev/null +++ b/spec/PhpSpec/Util/ClassFileAnalyserSpec.php @@ -0,0 +1,97 @@ +getSingleMethodClass(); + $this->getStartLineOfFirstMethod($class)->shouldReturn(7); + } + + public function it_should_detect_if_class_has_a_method() + { + $class = $this->getSingleMethodClass(); + $this->classHasMethods($class)->shouldReturn(true); + } + + public function it_should_detect_if_class_has_no_methods() + { + $class = $this->getClassWithNoMethods(); + $this->classHasMethods($class)->shouldReturn(false); + } + + public function it_should_return_the_line_number_of_the_end_of_the_named_method() + { + $class = $this->getSingleMethodClass(); + $this->getEndLineOfNamedMethod($class, 'methodOne')->shouldReturn(13); + } + + public function it_should_return_the_line_number_of_the_end_of_the_last_method() + { + $class = $this->getSingleMethodClassContainingAnonymousFunction(); + $this->getEndLineOfLastMethod($class)->shouldReturn(12); + } + + private function getSingleMethodClass() + { + return <<shouldHaveType(NameChecker::class); + } + + public function it_treats_normal_class_name_as_valid() + { + $this->isNameValid('Acme\Foo\Markdown')->shouldReturn(true); + } + + public function it_treats_class_name_with_reserved_keyword_chunk_in_the_middle_as_invalid() + { + $this->isNameValid('Acme\Namespace\Markdown')->shouldReturn(false); + } + + public function it_treats_class_name_with_reserved_keyword_chunk_on_the_left_side_as_invalid() + { + $this->isNameValid('Acme\Foo\List')->shouldReturn(false); + } + + public function it_treats_class_name_without_namespace_using_reserved_keywords_as_invalid() + { + $this->isNameValid('while')->shouldReturn(false); + } + + public function it_detects_invalid_class_name_in_any_letter_case() + { + $this->isNameValid('WHILE')->shouldReturn(false); + } +} diff --git a/spec/PhpSpec/Util/ExampleObjectUsingTrait.php b/spec/PhpSpec/Util/ExampleObjectUsingTrait.php new file mode 100644 index 0000000..1e14da9 --- /dev/null +++ b/spec/PhpSpec/Util/ExampleObjectUsingTrait.php @@ -0,0 +1,10 @@ +instantiate('spec\PhpSpec\Util\NoConstructor') + ->shouldBeAnInstanceOf('spec\PhpSpec\Util\NoConstructor'); + } + + public function it_creates_an_instance_ignoring_constructor() + { + $this->instantiate('spec\PhpSpec\Util\WithConstructor') + ->shouldBeAnInstanceOf('spec\PhpSpec\Util\WithConstructor'); + } + + public function it_creates_an_instance_with_properties() + { + $this->instantiate('spec\PhpSpec\Util\WithProperties') + ->shouldBeAnInstanceOf('spec\PhpSpec\Util\WithProperties'); + } + + public function it_complains_if_class_does_not_exist() + { + $this->shouldThrow('PhpSpec\Exception\Fracture\ClassNotFoundException') + ->duringInstantiate('NonExistingClass'); + } +} + +class NoConstructor +{ +} + +class WithConstructor +{ + private $requiredArgument; + + public function __construct($requiredArgument) + { + $this->requiredArgument = $requiredArgument; + } +} + +class WithProperties +{ + private $foo; + + protected $bar; + + public $baz; +} diff --git a/spec/PhpSpec/Util/MethodAnalyserSpec.php b/spec/PhpSpec/Util/MethodAnalyserSpec.php new file mode 100644 index 0000000..aebd11f --- /dev/null +++ b/spec/PhpSpec/Util/MethodAnalyserSpec.php @@ -0,0 +1,102 @@ +methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'emptyMethod')->shouldReturn(true); + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'emptyMethod2')->shouldReturn(true); + } + + public function it_identifies_commented_methods_as_empty() + { + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'commentedMethod')->shouldReturn(true); + } + + public function it_identifies_methods_with_code_as_not_empty() + { + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'nonEmptyMethod')->shouldReturn(false); + } + + public function it_identifies_methods_without_standard_braces_as_non_empty() + { + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'nonEmptyOneLineMethod')->shouldReturn(false); + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'nonEmptyOneLineMethod2')->shouldReturn(false); + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'nonEmptyOneLineMethod3')->shouldReturn(false); + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObject', 'nonEmptyMethod2')->shouldReturn(false); + } + + public function it_identifies_internal_classes_as_non_empty() + { + $this->methodIsEmpty('DateTimeZone', 'getOffset')->shouldReturn(false); + } + + public function it_identifies_methods_from_traits() + { + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObjectUsingTrait', 'emptyMethodInTrait')->shouldReturn(true); + $this->methodIsEmpty('spec\PhpSpec\Util\ExampleObjectUsingTrait', 'nonEmptyMethodInTrait')->shouldReturn(false); + } + + public function it_finds_the_real_declaring_class_of_a_method() + { + $this->getMethodOwnerName('spec\PhpSpec\Util\ExampleObjectUsingTrait', 'emptyMethodInTrait') + ->shouldReturn('spec\PhpSpec\Util\ExampleTrait'); + } +} + +class ExampleObject +{ + public function emptyMethod() + { + } + + public function emptyMethod2() + { + } + + public function commentedMethod() + { + /** + * this is a comment + */ + + // This is a comment + + /* this is a comment {} */ + } + + public function nonEmptyMethod() + { + /** + * a comment to fool us + */ + $variable = true; + // another comment + } + + public function nonEmptyMethod2() + { + return 'foo'; + } + + public function nonEmptyOneLineMethod() + { + return 'foo'; + } + + public function nonEmptyOneLineMethod2() + { + return 'foo'; + } + + public function nonEmptyOneLineMethod3() + { + return 'foo'; + } +} diff --git a/spec/PhpSpec/Util/ReservedWordsMethodNameCheckerSpec.php b/spec/PhpSpec/Util/ReservedWordsMethodNameCheckerSpec.php new file mode 100644 index 0000000..ff6bc3d --- /dev/null +++ b/spec/PhpSpec/Util/ReservedWordsMethodNameCheckerSpec.php @@ -0,0 +1,25 @@ +shouldHaveType('PhpSpec\Util\NameChecker'); + } + + public function it_returns_true_for_not_php_restricted_name() + { + $this->isNameValid('foo')->shouldReturn(true); + } + + public function it_returns_false_for___halt_compiler_function() + { + $this->isNameValid('__halt_compiler')->shouldReturn(false); + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/CallerSpec.php b/spec/PhpSpec/Wrapper/Subject/CallerSpec.php new file mode 100644 index 0000000..70109cd --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/CallerSpec.php @@ -0,0 +1,298 @@ +beConstructedWith( + $wrappedObject, + $example, + $dispatcher, + $exceptions, + $wrapper, + $accessInspector + ); + $wrapper->wrap(Argument::cetera())->willReturn($subject); + + $wrappedObject->isInstantiated()->willReturn(false); + $wrappedObject->getClassName()->willReturn(null); + $wrappedObject->getInstance()->willReturn(null); + $exceptions->propertyNotFound(Argument::cetera())->willReturn(new PropertyNotFoundException('Message', 'subject', 'prop')); + + $accessInspector->isMethodCallable(Argument::cetera())->willReturn(false); + } + + public function it_dispatches_method_call_events( + Dispatcher $dispatcher, + WrappedObject $wrappedObject, + AccessInspector $accessInspector + ) { + $wrappedObject->isInstantiated()->willReturn(true); + $wrappedObject->getInstance()->willReturn(new \ArrayObject()); + + $accessInspector->isMethodCallable(Argument::type('ArrayObject'), 'count')->willReturn(true); + + $dispatcher->dispatch( + 'beforeMethodCall', + Argument::type('PhpSpec\Event\MethodCallEvent') + )->shouldBeCalled(); + + $dispatcher->dispatch( + 'afterMethodCall', + Argument::type('PhpSpec\Event\MethodCallEvent') + )->shouldBeCalled(); + + $this->call('count'); + } + + public function it_sets_a_property_on_the_wrapped_object( + WrappedObject $wrappedObject, + AccessInspector $accessInspector, + Wrapper $wrapper + ) { + $obj = new \stdClass(); + $obj->id = 1; + + $accessInspector->isPropertyWritable( + Argument::type('stdClass'), + 'id' + )->willReturn('true'); + + $accessInspector->isPropertyReadable( + Argument::type('stdClass'), + 'id' + )->willReturn('true'); + + $wrappedObject->isInstantiated()->willReturn(true); + $wrappedObject->getInstance()->willReturn($obj); + + $wrapper->wrap(2)->willReturn(2); + + $this->set('id', 2); + if ($obj->id !== 2) { + throw new FailureException(); + } + } + + public function it_proxies_method_calls_to_wrapped_object( + \ArrayObject $obj, + WrappedObject $wrappedObject, + AccessInspector $accessInspector + ) { + $obj->asort()->shouldBeCalled(); + + $wrappedObject->isInstantiated()->willReturn(true); + $wrappedObject->getInstance()->willReturn($obj); + + $accessInspector->isMethodCallable(Argument::type('ArrayObject'), 'asort')->willReturn(true); + + $this->call('asort'); + } + + public function it_delegates_throwing_class_not_found_exception(WrappedObject $wrappedObject, ExceptionFactory $exceptions) + { + $wrappedObject->isInstantiated()->willReturn(false); + $wrappedObject->getClassName()->willReturn('Foo'); + + $exceptions->classNotFound('Foo') + ->willReturn(new \PhpSpec\Exception\Fracture\ClassNotFoundException( + 'Class "Foo" does not exist.', + '"Foo"' + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Fracture\ClassNotFoundException') + ->duringGetWrappedObject(); + } + + public function it_delegates_throwing_method_not_found_exception( + WrappedObject $wrappedObject, + ExceptionFactory $exceptions, + AccessInspector $accessInspector + ) { + $obj = new \ArrayObject(); + + $wrappedObject->isInstantiated()->willReturn(true); + $wrappedObject->getInstance()->willReturn($obj); + $wrappedObject->getClassName()->willReturn('ArrayObject'); + + $accessInspector->isMethodCallable($obj, 'foo')->willReturn(false); + + $exceptions->methodNotFound('ArrayObject', 'foo', []) + ->willReturn(new \PhpSpec\Exception\Fracture\MethodNotFoundException( + 'Method "foo" not found.', + $obj, + '"ArrayObject::foo"', + [] + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Fracture\MethodNotFoundException') + ->duringCall('foo'); + } + + public function it_delegates_throwing_method_not_found_exception_for_constructor(WrappedObject $wrappedObject, ExceptionFactory $exceptions, \stdClass $argument) + { + $obj = new ExampleClass(); + + $wrappedObject->isInstantiated()->willReturn(false); + $wrappedObject->getInstance()->willReturn(null); + $wrappedObject->getArguments()->willReturn([$argument]); + $wrappedObject->getClassName()->willReturn('spec\PhpSpec\Wrapper\Subject\ExampleClass'); + $wrappedObject->getFactoryMethod()->willReturn(null); + + $exceptions->methodNotFound('spec\PhpSpec\Wrapper\Subject\ExampleClass', '__construct', [$argument]) + ->willReturn(new \PhpSpec\Exception\Fracture\MethodNotFoundException( + 'Method "__construct" not found.', + $obj, + '"ExampleClass::__construct"', + [] + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Fracture\MethodNotFoundException') + ->duringCall('__construct'); + } + + public function it_delegates_throwing_named_constructor_not_found_exception(WrappedObject $wrappedObject, ExceptionFactory $exceptions) + { + $obj = new \ArrayObject(); + $arguments = ['firstname', 'lastname']; + + $wrappedObject->isInstantiated()->willReturn(false); + $wrappedObject->getInstance()->willReturn(null); + $wrappedObject->getClassName()->willReturn('ArrayObject'); + $wrappedObject->getFactoryMethod()->willReturn('register'); + $wrappedObject->getArguments()->willReturn($arguments); + + $exceptions->namedConstructorNotFound('ArrayObject', 'register', $arguments) + ->willReturn(new \PhpSpec\Exception\Fracture\NamedConstructorNotFoundException( + 'Named constructor "register" not found.', + $obj, + '"ArrayObject::register"', + [] + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Fracture\NamedConstructorNotFoundException') + ->duringCall('foo'); + } + + public function it_delegates_throwing_method_not_visible_exception( + WrappedObject $wrappedObject, + ExceptionFactory $exceptions, + AccessInspector $accessInspector + ) { + $obj = new ExampleClass(); + + $wrappedObject->isInstantiated()->willReturn(true); + $wrappedObject->getInstance()->willReturn($obj); + $wrappedObject->getClassName()->willReturn('spec\PhpSpec\Wrapper\Subject\ExampleClass'); + + $accessInspector->isMethodCallable($obj, 'privateMethod')->willReturn(false); + + $exceptions->methodNotVisible('spec\PhpSpec\Wrapper\Subject\ExampleClass', 'privateMethod', []) + ->willReturn(new \PhpSpec\Exception\Fracture\MethodNotVisibleException( + 'Method "privateMethod" not visible.', + $obj, + '"ExampleClass::privateMethod"', + [] + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Fracture\MethodNotVisibleException') + ->duringCall('privateMethod'); + } + + public function it_delegates_throwing_property_not_found_exception( + WrappedObject $wrappedObject, + ExceptionFactory $exceptions, + AccessInspector $accessInspector + ) { + $obj = new ExampleClass(); + + $wrappedObject->isInstantiated()->willReturn(true); + $wrappedObject->getInstance()->willReturn($obj); + + $accessInspector->isPropertyWritable($obj, 'nonExistentProperty')->willReturn(false); + + $exceptions->propertyNotFound($obj, 'nonExistentProperty') + ->willReturn(new \PhpSpec\Exception\Fracture\PropertyNotFoundException( + 'Property "nonExistentProperty" not found.', + $obj, + 'nonExistentProperty' + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Fracture\PropertyNotFoundException') + ->duringSet('nonExistentProperty', 'any value'); + } + + public function it_delegates_throwing_calling_method_on_non_object_exception(ExceptionFactory $exceptions) + { + $exceptions->callingMethodOnNonObject('foo') + ->willReturn(new \PhpSpec\Exception\Wrapper\SubjectException( + 'Call to a member function "foo()" on a non-object.' + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Wrapper\SubjectException') + ->duringCall('foo'); + } + + public function it_delegates_throwing_setting_property_on_non_object_exception(ExceptionFactory $exceptions) + { + $exceptions->settingPropertyOnNonObject('foo') + ->willReturn(new \PhpSpec\Exception\Wrapper\SubjectException( + 'Setting property "foo" on a non-object.' + )) + ->shouldBeCalled(); + $this->shouldThrow('\PhpSpec\Exception\Wrapper\SubjectException') + ->duringSet('foo'); + } + + public function it_delegates_throwing_getting_property_on_non_object_exception(ExceptionFactory $exceptions) + { + $exceptions->gettingPropertyOnNonObject('foo') + ->willReturn(new \PhpSpec\Exception\Wrapper\SubjectException( + 'Getting property "foo" on a non-object.' + )) + ->shouldBeCalled(); + + $this->shouldThrow('\PhpSpec\Exception\Wrapper\SubjectException') + ->duringGet('foo'); + } +} + +class ExampleClass +{ + private function privateMethod() + { + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/Expectation/ConstructorDecoratorSpec.php b/spec/PhpSpec/Wrapper/Subject/Expectation/ConstructorDecoratorSpec.php new file mode 100644 index 0000000..9dda5a8 --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/Expectation/ConstructorDecoratorSpec.php @@ -0,0 +1,38 @@ +beConstructedWith($expectation); + } + + public function it_rethrows_php_errors_as_phpspec_error_exceptions(Subject $subject, WrappedObject $wrapped) + { + $subject->callOnWrappedObject('getWrappedObject', [])->willThrow('PhpSpec\Exception\Example\ErrorException'); + $this->shouldThrow('PhpSpec\Exception\Example\ErrorException')->duringMatch('be', $subject, [], $wrapped); + } + + public function it_rethrows_fracture_errors_as_phpspec_error_exceptions(Subject $subject, WrappedObject $wrapped) + { + $subject->__call('getWrappedObject', [])->willThrow('PhpSpec\Exception\Fracture\FractureException'); + $this->shouldThrow('PhpSpec\Exception\Fracture\FractureException')->duringMatch('be', $subject, [], $wrapped); + } + + public function it_ignores_any_other_exception(Subject $subject, WrappedObject $wrapped) + { + $subject->callOnWrappedObject('getWrappedObject', [])->willThrow('\Exception'); + $wrapped->getClassName()->willReturn('\stdClass'); + $this->shouldNotThrow('\Exception')->duringMatch('be', $subject, [], $wrapped); + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/Expectation/DecoratorSpec.php b/spec/PhpSpec/Wrapper/Subject/Expectation/DecoratorSpec.php new file mode 100644 index 0000000..5ff0000 --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/Expectation/DecoratorSpec.php @@ -0,0 +1,40 @@ +beAnInstanceOf('spec\PhpSpec\Wrapper\Subject\Expectation\Decorator'); + $this->beConstructedWith($expectation); + } + + public function it_returns_the_decorated_expectation(Expectation $expectation) + { + $this->getExpectation()->shouldReturn($expectation); + } + + public function it_keeps_looking_for_nested_expectations(AbstractDecorator $decorator, Expectation $expectation) + { + $decorator->getExpectation()->willReturn($expectation); + $this->beAnInstanceOf('spec\PhpSpec\Wrapper\Subject\Expectation\Decorator'); + $this->beConstructedWith($decorator); + + $this->getNestedExpectation()->shouldReturn($expectation); + } +} + +class Decorator extends AbstractDecorator +{ + public function match(string $alias, $subject, array $arguments = []) + { + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/Expectation/DispatcherDecoratorSpec.php b/spec/PhpSpec/Wrapper/Subject/Expectation/DispatcherDecoratorSpec.php new file mode 100644 index 0000000..f713f78 --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/Expectation/DispatcherDecoratorSpec.php @@ -0,0 +1,67 @@ +beConstructedWith($expectation, $dispatcher, $matcher, $example); + } + + public function it_implements_the_interface_of_the_decorated() + { + $this->shouldImplement('PhpSpec\Wrapper\Subject\Expectation\Expectation'); + } + + public function it_dispatches_before_and_after_events(EventDispatcherInterface $dispatcher) + { + $alias = 'be'; + $subject = new \stdClass(); + $arguments = []; + + $dispatcher->dispatch('beforeExpectation', Argument::type('PhpSpec\Event\ExpectationEvent'))->shouldBeCalled(); + $dispatcher->dispatch('afterExpectation', Argument::which('getResult', ExpectationEvent::PASSED))->shouldBeCalled(); + $this->match($alias, $subject, $arguments); + } + + public function it_decorates_expectation_with_failed_event(Expectation $expectation, EventDispatcherInterface $dispatcher) + { + $alias = 'be'; + $subject = new \stdClass(); + $arguments = []; + + $expectation->match(Argument::cetera())->willThrow('PhpSpec\Exception\Example\FailureException'); + + $dispatcher->dispatch('beforeExpectation', Argument::type('PhpSpec\Event\ExpectationEvent'))->shouldBeCalled(); + $dispatcher->dispatch('afterExpectation', Argument::which('getResult', ExpectationEvent::FAILED))->shouldBeCalled(); + + $this->shouldThrow('PhpSpec\Exception\Example\FailureException')->duringMatch($alias, $subject, $arguments); + } + + public function it_decorates_expectation_with_broken_event(Expectation $expectation, EventDispatcherInterface $dispatcher) + { + $alias = 'be'; + $subject = new \stdClass(); + $arguments = []; + + $expectation->match(Argument::cetera())->willThrow('\RuntimeException'); + + $dispatcher->dispatch('beforeExpectation', Argument::type('PhpSpec\Event\ExpectationEvent'))->shouldBeCalled(); + $dispatcher->dispatch('afterExpectation', Argument::which('getResult', ExpectationEvent::BROKEN))->shouldBeCalled(); + + $this->shouldThrow('\RuntimeException')->duringMatch($alias, $subject, $arguments); + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/Expectation/NegativeSpec.php b/spec/PhpSpec/Wrapper/Subject/Expectation/NegativeSpec.php new file mode 100644 index 0000000..004133e --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/Expectation/NegativeSpec.php @@ -0,0 +1,27 @@ +beConstructedWith($matcher); + } + + public function it_calls_a_negative_match_on_matcher(Matcher $matcher) + { + $alias = 'somealias'; + $subject = 'subject'; + $arguments = []; + + $matcher->negativeMatch($alias, $subject, $arguments)->shouldBeCalled(); + $this->match($alias, $subject, $arguments); + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/Expectation/PositiveSpec.php b/spec/PhpSpec/Wrapper/Subject/Expectation/PositiveSpec.php new file mode 100644 index 0000000..16d324e --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/Expectation/PositiveSpec.php @@ -0,0 +1,26 @@ +beConstructedWith($matcher); + } + + public function it_calls_a_positive_match_on_matcher(Matcher $matcher) + { + $alias = 'somealias'; + $subject = 'subject'; + $arguments = []; + + $matcher->positiveMatch($alias, $subject, $arguments)->shouldBeCalled(); + $this->match($alias, $subject, $arguments); + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/ExpectationFactorySpec.php b/spec/PhpSpec/Wrapper/Subject/ExpectationFactorySpec.php new file mode 100644 index 0000000..5843c72 --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/ExpectationFactorySpec.php @@ -0,0 +1,84 @@ +beConstructedWith($example, $dispatcher, $matchers); + } + + public function it_creates_positive_expectations(MatcherManager $matchers, Matcher $matcher, Subject $subject) + { + $matchers->find(Argument::cetera())->willReturn($matcher); + + $subject->__call('getWrappedObject', [])->willReturn(new \stdClass()); + $decoratedExpecation = $this->create('shouldBe', $subject); + + $decoratedExpecation->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\Decorator'); + $decoratedExpecation->getNestedExpectation()->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\Positive'); + } + + public function it_creates_negative_expectations(MatcherManager $matchers, Matcher $matcher, Subject $subject) + { + $matchers->find(Argument::cetera())->willReturn($matcher); + + $subject->__call('getWrappedObject', [])->willReturn(new \stdClass()); + $decoratedExpecation = $this->create('shouldNotbe', $subject); + + $decoratedExpecation->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\Decorator'); + $decoratedExpecation->getNestedExpectation()->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\Negative'); + } + + public function it_creates_positive_throw_expectations(MatcherManager $matchers, Matcher $matcher, Subject $subject) + { + $matchers->find(Argument::cetera())->willReturn($matcher); + + $subject->__call('getWrappedObject', [])->willReturn(new \stdClass()); + $expectation = $this->create('shouldThrow', $subject); + + $expectation->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\PositiveThrow'); + } + + public function it_creates_negative_throw_expectations(MatcherManager $matchers, Matcher $matcher, Subject $subject) + { + $matchers->find(Argument::cetera())->willReturn($matcher); + + $subject->__call('getWrappedObject', [])->willReturn(new \stdClass()); + $expectation = $this->create('shouldNotThrow', $subject); + + $expectation->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\NegativeThrow'); + } + + public function it_creates_positive_trigger_expectations(MatcherManager $matchers, Matcher $matcher, Subject $subject) + { + $matchers->find(Argument::cetera())->willReturn($matcher); + + $subject->__call('getWrappedObject', [])->willReturn(new \stdClass()); + $expectation = $this->create('shouldTrigger', $subject); + + $expectation->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\PositiveTrigger'); + } + + public function it_creates_negative_trigger_expectations(MatcherManager $matchers, Matcher $matcher, Subject $subject) + { + $matchers->find(Argument::cetera())->willReturn($matcher); + + $subject->__call('getWrappedObject', [])->willReturn(new \stdClass()); + $expectation = $this->create('shouldNotTrigger', $subject); + + $expectation->shouldHaveType('PhpSpec\Wrapper\Subject\Expectation\NegativeTrigger'); + } +} diff --git a/spec/PhpSpec/Wrapper/Subject/WrappedObjectSpec.php b/spec/PhpSpec/Wrapper/Subject/WrappedObjectSpec.php new file mode 100644 index 0000000..652ede5 --- /dev/null +++ b/spec/PhpSpec/Wrapper/Subject/WrappedObjectSpec.php @@ -0,0 +1,102 @@ +beConstructedWith(null, $presenter); + } + + public function it_instantiates_object_using_classname() + { + $this->callOnWrappedObject('beAnInstanceOf', ['ArrayObject']); + $this->instantiate()->shouldHaveType('ArrayObject'); + } + + public function it_keeps_instantiated_object() + { + $this->callOnWrappedObject('beAnInstanceOf', ['ArrayObject']); + $this->instantiate()->shouldBeEqualTo($this->getInstance()); + } + + public function it_can_be_instantiated_with_a_factory_method() + { + $this->callOnWrappedObject( + 'beConstructedThrough', + [ + '\DateTime::createFromFormat', + ['d-m-Y', '01-01-1970'] + ] + ); + $this->instantiate()->shouldHaveType('\DateTime'); + } + + public function it_can_be_instantiated_with_a_factory_method_with_method_name_only() + { + $this->callOnWrappedObject('beAnInstanceOf', ['\DateTime']); + $this->callOnWrappedObject( + 'beConstructedThrough', + [ + 'createFromFormat', + ['d-m-Y', '01-01-1970'] + ] + ); + $this->instantiate()->shouldHaveType('\DateTime'); + } + + public function it_throws_an_exception_when_factory_method_returns_a_non_object() + { + $this->callOnWrappedObject('beAnInstanceOf', ['\DateTimeZone']); + $this->callOnWrappedObject('beConstructedThrough', ['listAbbreviations']); + + $message = 'The method \DateTimeZone::listAbbreviations did not return an object, returned array instead'; + $this->shouldThrow(new FactoryDoesNotReturnObjectException($message))->duringInstantiate(); + } + + public function it_throws_an_exception_when_trying_to_change_constructor_params_after_instantiation() + { + $this->callOnWrappedObject('beAnInstanceOf', ['\DateTime']); + + $this->callOnWrappedObject('beConstructedWith', [['now']]); + $this->callOnWrappedObject('instantiate', []); + $this->shouldThrow('PhpSpec\Exception\Wrapper\SubjectException')->duringBeConstructedWith(['tomorrow']); + } + + public function it_throws_an_exception_when_trying_to_change_factory_method_after_instantiation() + { + $this->callOnWrappedObject('beAnInstanceOf', ['\DateTime']); + + $this->callOnWrappedObject('beConstructedThrough', ['createFromFormat',['d-m-Y', '01-01-1980']]); + $this->callOnWrappedObject('instantiate', []); + $this->shouldThrow('PhpSpec\Exception\Wrapper\SubjectException') + ->duringBeConstructedThrough(['createFromFormat',['d-m-Y', '01-01-1970']]); + } + + public function it_throws_an_exception_when_trying_to_change_from_constructor_to_factory_method_after_instantiation() + { + $this->callOnWrappedObject('beAnInstanceOf', ['\DateTime']); + + $this->callOnWrappedObject('beConstructedWith', [['now']]); + $this->callOnWrappedObject('instantiate', []); + $this->shouldThrow('PhpSpec\Exception\Wrapper\SubjectException') + ->duringBeConstructedThrough(['createFromFormat',['d-m-Y', '01-01-1970']]); + } + + public function it_throws_an_exception_when_trying_to_change_from_factory_method_to_constructor_after_instantiation() + { + $this->callOnWrappedObject('beAnInstanceOf', ['\DateTime']); + + $this->callOnWrappedObject('beConstructedThrough', ['createFromFormat',['d-m-Y', '01-01-1980']]); + $this->callOnWrappedObject('instantiate', []); + $this->shouldThrow('PhpSpec\Exception\Wrapper\SubjectException')->duringBeConstructedWith(['tomorrow']); + } +} diff --git a/spec/PhpSpec/Wrapper/SubjectSpec.php b/spec/PhpSpec/Wrapper/SubjectSpec.php new file mode 100644 index 0000000..1089711 --- /dev/null +++ b/spec/PhpSpec/Wrapper/SubjectSpec.php @@ -0,0 +1,78 @@ +beConstructedWith( + null, + $wrapper, + $wrappedObject, + $caller, + $arrayAccess, + $expectationFactory, + $accessInspector + ); + } + + public function it_passes_the_created_subject_to_expectation( + WrappedObject $wrappedObject, + ExpectationFactory $expectationFactory, + Expectation $expectation + ) { + $expectation->match(Argument::cetera())->willReturn(true); + $wrappedObject->getClassName()->willReturn('spec\PhpSpec\Wrapper\Everything'); + $expectationFactory->create(Argument::cetera())->willReturn($expectation); + + $this->callOnWrappedObject('shouldBeAlright'); + $expectationFactory->create(Argument::any(), Argument::type('spec\PhpSpec\Wrapper\Everything'), Argument::any())->shouldHaveBeenCalled(); + } + + public function it_passes_the_existing_subject_to_expectation( + Wrapper $wrapper, + WrappedObject $wrappedObject, + Caller $caller, + SubjectWithArrayAccess $arrayAccess, + ExpectationFactory $expectationFactory, + Expectation $expectation + ) { + $existingSubject = new \ArrayObject(); + $this->beConstructedWith($existingSubject, $wrapper, $wrappedObject, $caller, $arrayAccess, $expectationFactory); + + $expectation->match(Argument::cetera())->willReturn(true); + $wrappedObject->getClassName()->willReturn('\ArrayObject'); + $expectationFactory->create(Argument::cetera())->willReturn($expectation); + + $this->callOnWrappedObject('shouldBeAlright'); + $expectationFactory->create(Argument::any(), Argument::exact($existingSubject), Argument::any())->shouldHaveBeenCalled(); + } +} + +class Everything +{ + public function isAlright() + { + return true; + } +} diff --git a/spec/Proget/Tests/PHPStan/PhpSpec/BarSpec.php b/spec/Proget/Tests/PHPStan/PhpSpec/BarSpec.php deleted file mode 100644 index 87ea703..0000000 --- a/spec/Proget/Tests/PHPStan/PhpSpec/BarSpec.php +++ /dev/null @@ -1,43 +0,0 @@ -beConstructedWith($foo); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(Bar::class); - } - - public function it_should_return_correct_string(Foo $foo): void - { - $foo->foo()->willReturn('correct-string'); - - $this->foo()->shouldReturn('correct-string'); - } - - public function it_should_throw_exception(Foo $foo): void - { - $foo->foo()->willReturn('shouldThrow'); - - $this->shouldThrow(\RuntimeException::class)->during('foo'); - } - - public function it_should_call_void_one_time(Foo $foo): void - { - $foo->doSomething()->shouldBeCalledTimes(1); - - $this->bar(); - } -} diff --git a/spec/Proget/Tests/PHPStan/PhpSpec/FooSpec.php b/spec/Proget/Tests/PHPStan/PhpSpec/FooSpec.php deleted file mode 100644 index 797b1c3..0000000 --- a/spec/Proget/Tests/PHPStan/PhpSpec/FooSpec.php +++ /dev/null @@ -1,44 +0,0 @@ -beConstructedWith($baz); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(Foo::class); - } - - public function it_should_return_empty_string(): void - { - $this->foo()->shouldBe(''); - } - - public function it_should_make_baz(Baz $baz): void - { - $baz->make()->willReturn(123); - - $this->makeBaz()->shouldBe(123); - } - - public function it_should_make_value_object_with_provided_string(): void - { - $vo = $this->makeValueObject('php-ml is awesome'); // little product placement XD - - $vo->shouldBeAnInstanceOf(ValueObject::class); - $vo->string()->shouldEqual('php-ml is awesome'); - $vo->copy()->copy()->string()->shouldEqual('php-ml is awesome'); - } -} diff --git a/src/Locator/SpecClassLocator.php b/src/Locator/SpecClassLocator.php index c07891e..eb0733b 100644 --- a/src/Locator/SpecClassLocator.php +++ b/src/Locator/SpecClassLocator.php @@ -23,7 +23,7 @@ public function locate(array $dirs): array }, iterator_to_array($finder->files())); return array_values(array_filter($classes, function (string $className): bool { - return preg_match('/Spec$/', $className) !== false; + return preg_match('/Spec$/', $className) !== false && class_exists($className); })); } diff --git a/src/Reflection/ObjectBehaviorMethodReflection.php b/src/Reflection/ObjectBehaviorMethodReflection.php new file mode 100644 index 0000000..c134a3d --- /dev/null +++ b/src/Reflection/ObjectBehaviorMethodReflection.php @@ -0,0 +1,62 @@ +wrappedReflection = $wrappedReflection; + } + + public function wrappedReflection(): MethodReflection + { + return $this->wrappedReflection; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->wrappedReflection->getDeclaringClass(); + } + + public function isStatic(): bool + { + return $this->wrappedReflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->wrappedReflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->wrappedReflection->isPublic(); + } + + public function getName(): string + { + return $this->wrappedReflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->wrappedReflection->getPrototype(); + } + + public function getVariants(): array + { + return $this->wrappedReflection->getVariants(); + } +} diff --git a/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php b/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php index b060452..acb4273 100644 --- a/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php +++ b/src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php @@ -9,6 +9,7 @@ use PhpSpec\Locator\ResourceLocator; use PhpSpec\ObjectBehavior; use PhpSpec\Util\Filesystem; +use PhpSpec\Wrapper\Subject; use PHPStan\Analyser\OutOfClassScope; use PHPStan\Broker\Broker; use PHPStan\Reflection\BrokerAwareExtension; @@ -16,7 +17,6 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; use Proget\PHPStan\PhpSpec\Exception\SpecSourceClassNotFound; -use Proget\PHPStan\PhpSpec\Type\SubjectType; final class ObjectBehaviorMethodsClassReflectionExtension implements MethodsClassReflectionExtension, BrokerAwareExtension { @@ -51,9 +51,10 @@ public function getMethod(ClassReflection $classReflection, string $methodName): if ($objectBehaviorReflection->hasMethod($methodName)) { return $objectBehaviorReflection->getMethod($methodName, new OutOfClassScope()); } - - if ($objectBehaviorReflection->hasNativeMethod($methodName)) { - return $objectBehaviorReflection->getNativeMethod($methodName); + // all calls on ObjectBehavior are also proxed to Subject + $subjectReflection = $this->broker->getClass(Subject::class); + if ($subjectReflection->hasMethod($methodName)) { + return $subjectReflection->getMethod($methodName, new OutOfClassScope()); } /** @var PSR0Resource[] $resources */ @@ -70,26 +71,6 @@ public function getMethod(ClassReflection $classReflection, string $methodName): $srcClassReflection = $this->broker->getClass($className); - $method = $srcClassReflection->getNativeMethod($methodName); - $this->replaceReturnType($method); - - return $method; - } - - private function replaceReturnType(MethodReflection $method): void - { - $method->getVariants(); - $methodReflection = new \ReflectionClass($method); - $returnType = $methodReflection->getProperty('returnType'); - $returnType->setAccessible(true); - $returnType->setValue($method, new SubjectType($returnType->getValue($method))); - - $nativeReturnType = $methodReflection->getProperty('nativeReturnType'); - $nativeReturnType->setAccessible(true); - $nativeReturnType->setValue($method, new SubjectType($nativeReturnType->getValue($method))); - - $variants = $methodReflection->getProperty('variants'); - $variants->setAccessible(true); - $variants->setValue($method, null); + return new ObjectBehaviorMethodReflection($srcClassReflection->getMethod($methodName, new OutOfClassScope())); } } diff --git a/src/Reflection/ObjectBehaviorPropertiesClassReflectionExtension.php b/src/Reflection/ObjectBehaviorPropertiesClassReflectionExtension.php new file mode 100644 index 0000000..b6165b2 --- /dev/null +++ b/src/Reflection/ObjectBehaviorPropertiesClassReflectionExtension.php @@ -0,0 +1,70 @@ +locator = new PSR0Locator(new Filesystem()); + } + + public function setBroker(Broker $broker): void + { + $this->broker = $broker; + } + + public function hasProperty(ClassReflection $classReflection, string $propertyName): bool + { + return in_array(ObjectBehavior::class, $classReflection->getParentClassesNames(), true); + } + + public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + { + /** @var PSR0Resource[] $resources */ + $resources = $this->locator->findResources((string) $classReflection->getFileName()); + + if (count($resources) === 0) { + throw new SpecSourceClassNotFound(sprintf('Source class from %s not found', $classReflection->getName())); + } + + $className = $resources[0]->getSrcClassname(); + if (!class_exists($className)) { + throw new SpecSourceClassNotFound(sprintf('Spec source class %s not found', $className)); + } + + $srcClassReflection = $this->broker->getClass($className); + if ($srcClassReflection->hasProperty($propertyName)) { + $srcClassReflection->getProperty($propertyName); + } + + //special case to handle magic proxy call in object behavior + if ($srcClassReflection->hasConstant($propertyName)) { + throw new \RuntimeException('Need to implement this'); + } + } +} diff --git a/src/Type/CollaboratorDynamicMethodReturnTypeExtension.php b/src/Type/CollaboratorDynamicMethodReturnTypeExtension.php index 7b12e3e..3a13861 100644 --- a/src/Type/CollaboratorDynamicMethodReturnTypeExtension.php +++ b/src/Type/CollaboratorDynamicMethodReturnTypeExtension.php @@ -31,7 +31,7 @@ public function getClass(): string public function isMethodSupported(MethodReflection $methodReflection): bool { - return true; + return $methodReflection->getDeclaringClass()->getName() === $this->className; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type diff --git a/src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php b/src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php new file mode 100644 index 0000000..a7cd70a --- /dev/null +++ b/src/Type/ObjectBehaviorDynamicMethodReturnTypeExtension.php @@ -0,0 +1,36 @@ +wrappedReflection()->getVariants()[0]->getReturnType()); + } +} diff --git a/tests/Bar.php b/tests/Bar.php deleted file mode 100644 index 5c1709a..0000000 --- a/tests/Bar.php +++ /dev/null @@ -1,32 +0,0 @@ -foo = $foo; - } - - public function foo(): string - { - if ($this->foo->foo() === 'shouldThrow') { - throw new \RuntimeException(); - } - - return $this->foo->foo(); - } - - public function bar(): void - { - $this->foo->doSomething(); - } -} diff --git a/tests/Baz.php b/tests/Baz.php deleted file mode 100644 index 98a1817..0000000 --- a/tests/Baz.php +++ /dev/null @@ -1,10 +0,0 @@ -baz = $baz; - } - - public function foo(): string - { - return ''; - } - - public function doSomething(): void - { - } - - public function makeBaz(): int - { - return $this->baz->make(); - } - - public function makeValueObject(string $string): ValueObject - { - return new ValueObject($string); - } -} diff --git a/tests/ValueObject.php b/tests/ValueObject.php deleted file mode 100644 index b1600b4..0000000 --- a/tests/ValueObject.php +++ /dev/null @@ -1,28 +0,0 @@ -string = $string; - } - - public function string(): string - { - return $this->string; - } - - public function copy(): self - { - return new self($this->string); - } -}