├── .codeclimate.yml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .rspec ├── .yardopts ├── Appraisals ├── CHANGELOG.md ├── Gemfile ├── README.md ├── Rakefile ├── bin ├── console ├── generate_requires ├── opal ├── opal-rspec └── setup ├── config.ru ├── diff-lcs └── spec │ ├── files_to_exclude.txt │ └── requires.rb ├── example ├── Gemfile ├── README.md ├── Rakefile ├── opal │ └── user.rb └── spec │ └── user_spec.rb ├── exe └── opal-rspec ├── lib-opal ├── opal-rspec.rb └── opal │ ├── rspec.rb │ └── rspec │ ├── async.rb │ ├── async │ ├── configuration.rb │ ├── example.rb │ ├── example_group.rb │ ├── hooks.rb │ ├── memoized_helpers.rb │ ├── reporter.rb │ └── runner.rb │ ├── autorun.rb │ ├── browser.rb │ ├── browser_early.rb │ ├── default_config.rb │ ├── fixes.rb │ ├── fixes │ ├── diff-lcs.rb │ ├── diff-lcs │ │ ├── hunk.rb │ │ └── lcs.rb │ ├── opal.rb │ ├── rspec.rb │ └── rspec │ │ ├── core.rb │ │ ├── core │ │ ├── configuration.rb │ │ ├── example_status_persister.rb │ │ ├── formatters.rb │ │ ├── formatters │ │ │ ├── deprecation_formatter.rb │ │ │ ├── exception_presenter.rb │ │ │ ├── loader.rb │ │ │ ├── snippet_extractor.rb │ │ │ └── syntax_highlighter.rb │ │ ├── metadata.rb │ │ ├── notifications.rb │ │ ├── notifications │ │ │ └── examples_notification.rb │ │ ├── ordering.rb │ │ └── ordering │ │ │ └── random.rb │ │ ├── example_groups.rb │ │ ├── expectations.rb │ │ ├── matchers.rb │ │ ├── matchers │ │ ├── built_in.rb │ │ ├── built_in │ │ │ ├── base_matcher.rb │ │ │ └── start_and_end_with.rb │ │ └── expecteds_for_multiple_diffs.rb │ │ ├── mocks.rb │ │ ├── mocks │ │ ├── error_generator.rb │ │ └── proxy.rb │ │ ├── support.rb │ │ └── support │ │ ├── differ.rb │ │ ├── encoded_string.rb │ │ ├── formatter_support.rb │ │ ├── ruby_features.rb │ │ └── source.rb │ ├── formatter │ ├── browser_formatter.rb │ ├── document_io.rb │ ├── element.rb │ ├── html_printer.rb │ └── noop_flush_string_io.rb │ ├── pre_require_fixes.rb │ ├── requires.rb │ ├── spec_opts.rb.erb │ └── sprockets_runner.rb.erb ├── lib ├── opal-rspec.rb └── opal │ ├── rspec.rb │ └── rspec │ ├── cached_environment.rb │ ├── configuration_parser.rb │ ├── locator.rb │ ├── project_initializer.rb │ ├── project_initializer │ ├── .rspec-opal │ └── spec-opal │ │ └── spec_helper.rb │ ├── rake_task.rb │ ├── runner.rb │ ├── sprockets.rb │ ├── sprockets_environment.rb │ ├── util.rb │ └── version.rb ├── opal-rspec.gemspec ├── rspec-core └── spec │ ├── files_to_exclude.txt │ ├── filters.rb │ ├── fixes │ ├── missing_constants.rb │ └── shared_examples.rb │ └── requires.rb ├── rspec-expectations └── spec │ ├── files_to_exclude.txt │ ├── filters.rb │ ├── fixes │ ├── missing_constants.rb │ └── shared_examples.rb │ └── requires.rb ├── rspec-mocks └── spec │ ├── files_to_exclude.txt │ ├── filters.rb │ ├── fixes │ ├── no_const_hide.rb │ └── shared_examples.rb │ └── requires.rb ├── rspec-support └── spec │ ├── files_to_exclude.txt │ ├── filters.rb │ ├── fixes │ ├── missing_constants.rb │ └── shared_examples.rb │ └── requires.rb ├── spec-opal-passing ├── spec_helper.rb └── tautology_spec.rb ├── spec-opal-rspec ├── core │ ├── example_group_spec.rb │ ├── failed_example_notification_spec.rb │ ├── hooks_spec.rb │ ├── memoized_helpers_spec.rb │ └── metadata_spec.rb ├── expectations │ ├── be_instance_of_spec.rb │ ├── dsl_spec.rb │ ├── expectation_target_spec.rb │ └── yield_spec.rb └── spec_helper.rb ├── spec-opal ├── after_hooks_spec.rb ├── around_hooks_spec.rb ├── async_spec.rb ├── before_hooks_spec.rb ├── browser-formatter │ └── opal_browser_formatter_spec.rb ├── example_spec.rb ├── matchers_spec.rb ├── mock_spec.rb ├── other │ ├── color_on_by_default_spec.rb │ ├── dummy_spec.rb │ ├── formatter_dependency.rb │ ├── ignored_spec.opal │ └── test_formatter.rb ├── should_syntax_spec.rb ├── skip_pending_spec.rb ├── spec_helper.rb ├── sprockets_runner_js_errors.rb.erb └── subject_spec.rb ├── spec ├── integration │ ├── browser_spec.rb │ ├── browser_spec.ru │ ├── spec_opts_spec.rb │ ├── verify_opal_specs_spec.rb │ ├── verify_other_specs_spec.rb │ └── verify_rspec_specs_spec.rb ├── opal │ └── rspec │ │ ├── browser_formatter_spec.rb │ │ ├── browser_formatter_spec.ru │ │ ├── cached_environment_spec.rb │ │ ├── locator_spec.rb │ │ ├── rake_task_spec.rb │ │ ├── runner_spec.rb │ │ ├── sprockets_environment_spec.rb │ │ └── temp_dir_helper.rb ├── rspec │ ├── filter_processor.rb │ ├── shared │ │ ├── opal │ │ │ ├── fixes │ │ │ │ ├── deprecation_helpers.rb │ │ │ │ └── rspec_helpers.rb │ │ │ └── progress_json_formatter.rb │ │ └── opal_filters.rb │ └── support │ │ ├── capybara.rb │ │ ├── upstream_tests.rb │ │ └── upstream_tests │ │ ├── config.rb │ │ ├── files_to_run.rb │ │ ├── result.rb │ │ └── runner.rb └── spec_helper.rb ├── stubs ├── cgi │ ├── escape.rb │ └── util.rb ├── coderay.rb ├── drb │ ├── acl.rb │ └── drb.rb ├── erb │ └── version.rb ├── minitest.rb ├── minitest │ ├── assertions.rb │ └── unit.rb ├── mutex_m.rb ├── open3.rb ├── psych.rb ├── ripper.rb ├── rubygems │ ├── bundler_version_finder.rb │ └── util.rb ├── socket.rb ├── tempfile.rb ├── test │ └── unit │ │ └── assertions.rb └── thread_order.rb └── tasks ├── building.rake └── testing.rake /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_paths: 2 | - rspec/**/* 3 | - rspec-core/**/* 4 | - rspec-expectations/**/* 5 | - rspec-mocks/**/* 6 | - rspec-support/**/* 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: ubuntu 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - "*-stable" 8 | - "*/ci-check" 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ 'ubuntu-latest' ] 19 | ruby: [ ruby-head, 3.2, 3.1, "3.0", 2.7 ] 20 | opal: [ master, 1.7, 1.8 ] 21 | 22 | runs-on: ${{ matrix.os }} 23 | 24 | env: 25 | OPAL_VERSION: ${{ matrix.opal }} 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: true 31 | - uses: ruby/setup-ruby@v1 32 | with: 33 | ruby-version: ${{ matrix.ruby }} 34 | - name: Setup project 35 | run: bin/setup 36 | - name: Run opal-rspec test 37 | run: "bundle exec exe/opal-rspec spec-opal-passing" 38 | - name: Run rspec test 39 | run: bundle exec rspec 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Gemfile.lock 3 | *.gem 4 | Gemfile.lock 5 | gemfiles/*.lock 6 | /tmp 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rspec"] 2 | path = rspec/upstream 3 | url = https://github.com/rspec/rspec-metagem.git 4 | [submodule "rspec-support"] 5 | path = rspec-support/upstream 6 | url = https://github.com/rspec/rspec-support.git 7 | [submodule "rspec-core"] 8 | path = rspec-core/upstream 9 | url = https://github.com/rspec/rspec-core.git 10 | [submodule "rspec-mocks"] 11 | path = rspec-mocks/upstream 12 | url = https://github.com/rspec/rspec-mocks.git 13 | [submodule "rspec-expectations"] 14 | path = rspec-expectations/upstream 15 | url = https://github.com/rspec/rspec-expectations.git 16 | [submodule "diff-lcs/upstream"] 17 | path = diff-lcs/upstream 18 | url = https://github.com/halostatue/diff-lcs 19 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | -c 2 | -f d 3 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | opal/**/*.rb 3 | --markup markdown 4 | - 5 | CHANGELOG.md 6 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise 'opal-0.11' do 2 | gem 'opal', '~> 0.11.0' 3 | gem 'opal-sprockets' 4 | end 5 | 6 | appraise 'opal-master' do 7 | gem 'opal', git: 'https://github.com/opal/opal.git' 8 | gem 'opal-sprockets', github: 'opal/opal-sprockets' 9 | end 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Opal-RSpec Changelog 2 | 3 | ## 1.1.0.alpha3 - 2023-09-26 4 | 5 | - Refactor glob_to_re into a robust implementation 6 | 7 | - Add `--opal-opt` CLI option 8 | 9 | - Add `--rbrequire` CLI option 10 | 11 | 12 | ## 1.1.0.alpha2 - 2023-09-20 13 | 14 | - Drop advertised support for Opal v1.6 15 | 16 | - Fix support for Ruby 2.7+ 17 | 18 | - Drop requirement of Opal-Sprockets 19 | 20 | 21 | ## 1.1.0.alpha1 - 2023-09-16 22 | 23 | - Support Opal headless browser runners 24 | 25 | - (Almost) full support for `rspec` CLI util (which we run using `opal-rspec` command) 26 | * Focus works, you can type `opal-rspec spec-opal-passing/tautology_spec.rb:8` to select a given test 27 | * Most of other command line switches of `opal-rspec` work 28 | * You can use a `.rspec-opal` configuration file akin to `.rspec` with the regular RSpec 29 | * Just like with RSpec, switches set in this file propagate everywhere (eg. Rake task), so this is from now a prefered place to set up paths, etc. 30 | 31 | - Compatibility for upcoming Opal v1.8 32 | 33 | 34 | ## 1.0.0 - 2022-11-24 35 | 36 | - Drop support for anything below Opal v1.6.alpha1 37 | 38 | - Update to the latest RSpec versions 39 | 40 | - Vendor-in `diff-lcs` 41 | 42 | - Rework the async logic to use the `await` feature of Opal 43 | * If you use async features, it's crucial to use a `# await: *await*` magic comment (this will cause any call to a method containing an `await` word to be compiled with an `await` ES8 keyword) 44 | * Both `let` and `subject` that return a promise (ie. are async) must be referenced with an `.await` method 45 | * In `around` blocks, you must call `example.run_await` instead of just `example.run` 46 | * Only `PromiseV2` is supported (`PromiseV1` may work, but you should migrate your application to use `PromiseV2` nevertheless, in Opal 2.0 it will become the default) 47 | 48 | - Drop a requirement of `opal-sprockets` 49 | * Sprockets support is still provided, but you need to manually `require "opal/rspec/sprockets"` 50 | 51 | 52 | ## 0.8.0 - 2021-12-01 53 | 54 | - Support for Opal v1.x 55 | 56 | - Fix some x-srting semicolon warnings 57 | 58 | - Fix forwarding the exit code to the rake task 59 | 60 | 61 | ## 0.7.1 - 2019-02-04 62 | 63 | - add a project initializer: `opal-rspec --init` 64 | 65 | 66 | ## 0.7.0 - 2019-02-03 67 | 68 | - Drop support for the legacy async syntax 69 | 70 | - Complete internal overhaul 71 | 72 | - Support for Opal v0.11 73 | 74 | 75 | ## 0.6.1 - 2017-05-25 76 | 77 | - Fixed compatibility with Rake 12 78 | 79 | - Open up to opal 0.11 (not officially supported yet) 80 | 81 | 82 | ## 0.6.0 - 2016-08-29 83 | 84 | - Support for Opal 0.8/0.9 removed 85 | 86 | - Opal 0.10 support 87 | 88 | - Arity checking enabled by default 89 | 90 | - Dropped support for PhantomJS < 2.0 91 | 92 | - Removed `Kernel#caller` monkey patch so test file/line metadata is only available if supplied via test metadata or for failures. Should improve performance since an exception isn't thrown for every test to gather the data 93 | 94 | 95 | ## 0.5.0 - 2015-12-08 96 | 97 | - By default, any subject, it example block, before(:each), after(:each), and around that returns a promise will be executed asynchronously. Async is NOT yet supported for context level hooks. Async approach from < 0.4.3 will still work. 98 | 99 | - Update to RSpec 3.1 (core is 3.1.7, expectations/support 3.1.2, mocks 3.1.3) 100 | 101 | - Opal 0.9 compatibility 102 | 103 | - A lot more aspects of RSpec should work now as 20+ Opal pull requests were completed from opal-rspec work 104 | 105 | - Remove copy of source from opal-rspec git repo (and just rely on git submodule fetch) 106 | 107 | - Rake task improvements: 108 | - supports passing a test pattern (include and exclude) and FileLists besides 'spec/**/*_spec.rb 109 | - colors, formatter, and additional requires can be supplied from the command line via the SPEC_OPTS environment variable 110 | 111 | - Formatters: 112 | - Fixed issues with RSpec's BaseTextFormatter and made ProgressFormatter the default when run via the Rake task 113 | - Fix redundant messages with expectation fails 114 | - Browser formatter now works w/ progress bar and has a 'Dump to console' link that will put a clickable stack trace for a failed example in the browser console 115 | - JSON formatter supported 116 | 117 | - Fixed issues with constants/example group naming 118 | 119 | - Basic nodejs runner support 120 | 121 | - A lot more matchers enabled 122 | 123 | * PhantomJS 2.0 compatibility (also still compatible with 1.9.8). Thanks to @aost. Closes out https://github.com/opal/opal-rspec/issues/42 124 | 125 | 126 | ## 0.4.3 - 2015-06-14 127 | 128 | - Allow the gem to be run under Opal 0.7 and 0.8 129 | - Fix some threading issues 130 | - Avoid some other calls to mutable-strings methods 131 | 132 | 133 | ## 0.4.2 - 2015-03-28 134 | 135 | - Avoid phantomjs warning messages 136 | 137 | 138 | ## 0.4.1 - 2015-02-25 139 | 140 | - Remove predicate matcher fixes as Opal supports $1..$9 special gvars. 141 | 142 | - Update Opal dependency for ~> 0.6.0. 143 | 144 | - Remove double-escaping in inline x-strings (from Opal bug fix). 145 | 146 | - Remove opal-sprockets dependency - build tools now part of opal. 147 | 148 | - Replaced browser formatter to use html printer from rspec 149 | 150 | - Add timeout support to asynchronous specs 151 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | unless Dir[__dir__ + '/rspec{,-{core,expectations,mocks,support}}/upstream'].any? 5 | raise 'Run: "git submodule update --init" to get RSpec sources' 6 | end 7 | 8 | # These need to come from our local path in order for create_requires.rb to work properly 9 | gem 'rspec', path: 'rspec/upstream' 10 | gem 'rspec-support', path: 'rspec-support/upstream' 11 | gem 'rspec-core', path: 'rspec-core/upstream' 12 | gem 'rspec-mocks', path: 'rspec-mocks/upstream' 13 | gem 'rspec-expectations', path: 'rspec-expectations/upstream' 14 | 15 | gem 'pry' 16 | 17 | case ENV['OPAL_VERSION'] 18 | when 'local' 19 | gem 'opal', path: '../opal' 20 | when /^[0-9]/ 21 | gem 'opal', "~> #{ENV['OPAL_VERSION']}.0a" 22 | when String 23 | gem 'opal', git: 'https://github.com/opal/opal.git', branch: ENV['OPAL_VERSION'] 24 | end 25 | 26 | gem 'opal-sprockets', '>=1.0' 27 | 28 | gem 'puma' 29 | gem 'rack', '<3' 30 | gem 'base64' 31 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | import 'tasks/testing.rake' 5 | import 'tasks/building.rake' 6 | 7 | task :default => [:rspec] 8 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "opal/rspec" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/generate_requires: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ROOT = "#{__dir__}/.." 4 | $LOAD_PATH.unshift "#{ROOT}/rspec/upstream/lib" 5 | $LOAD_PATH.unshift "#{ROOT}/rspec-core/upstream/lib" 6 | $LOAD_PATH.unshift "#{ROOT}/rspec-expectations/upstream/lib" 7 | $LOAD_PATH.unshift "#{ROOT}/rspec-mocks/upstream/lib" 8 | $LOAD_PATH.unshift "#{ROOT}/rspec-support/upstream/lib" 9 | 10 | require 'json' 11 | require 'pathname' 12 | 13 | # Opal will not have the built-in RNG, which affects the required outcome 14 | Object.send(:remove_const, :Random) 15 | 16 | # These scripts allow a leaner top level spec (like noted here) 17 | BASE_FILES = %w{rspec rspec/mocks rspec/expectations rspec/core rspec/core/mocking_adapters/rspec} 18 | FORMATTERS = %w{base_formatter base_text_formatter progress_formatter documentation_formatter html_printer json_formatter}.map {|f| "rspec/core/formatters/#{f}"} 19 | MATCHERS = Dir.glob('rspec-expectations/upstream/lib/rspec/matchers/built_in/**/*.rb').map do |each_file| 20 | path = Pathname.new(each_file).relative_path_from(Pathname.new('rspec-expectations/upstream/lib')).to_s 21 | path.sub File.extname(path), '' 22 | end 23 | MOCK_STUFF = %w{matchers/expectation_customization any_instance}.map { |f| "rspec/mocks/#{f}" } 24 | REQUIRES = BASE_FILES + FORMATTERS + MATCHERS + MOCK_STUFF 25 | 26 | # Should not need to edit below this 27 | 28 | ROOTS = Dir[__dir__+'/../rspec{,-{core,expectations,mocks,support}}/upstream/lib'].map {|root| File.expand_path(root)} 29 | ROOTS_REGEXP = /\A(#{ROOTS.map {|r| Regexp.escape r}.join('|')})\// 30 | 31 | module Kernel 32 | alias :require_before_opal_rspec :require 33 | def require path 34 | result = require_before_opal_rspec(path) 35 | puts "requiring: #{path} (#{result})" 36 | RSPEC_PATHS << path 37 | result 38 | end 39 | 40 | alias :require_relative_before_opal_rspec :require_relative 41 | def require_relative path 42 | base = File.dirname(caller(1,1).first) 43 | path_for_require = File.expand_path(path, base).sub(ROOTS_REGEXP, '') 44 | require path_for_require 45 | end 46 | end 47 | 48 | RSPEC_PATHS = [] 49 | REQUIRES.each {|r| require r } 50 | 51 | # Put top level items first 52 | requires = RSPEC_PATHS.uniq.sort 53 | 54 | File.open "#{ROOT}/lib-opal/opal/rspec/requires.rb", 'w' do |file| 55 | file.puts "# Generated automatically by #{$0}" 56 | requires.each do |path| 57 | file.puts "require '#{path}'" 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /bin/opal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | exec 'bundle', 'exec', 'opal', *ARGV 4 | -------------------------------------------------------------------------------- /bin/opal-rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | exec 'bundle', 'exec', 'opal-rspec', *ARGV 4 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | git submodule update --init 7 | bundle install 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'opal/rspec' 2 | require 'opal/sprockets/server' 3 | 4 | Opal::Config.source_map_enabled = false 5 | Opal::Config.arity_check_enabled = true 6 | 7 | sprockets_env = Opal::RSpec::SprocketsEnvironment.new(spec_pattern = 'spec-opal/**/*_spec.{rb,opal}', 8 | spec_exclude_pattern = nil, 9 | spec_files = nil, 10 | default_path = 'spec-opal') 11 | 12 | sprockets_env.cache = ::Sprockets::Cache::FileStore.new(File.join('tmp', 'cache', 'opal_specs')) 13 | run Opal::Sprockets::Server.new(sprockets: sprockets_env) { |s| 14 | s.main = 'sprockets_runner_js_errors' 15 | # sprockets_runner_js_errors will not be in the opal load path by default 16 | s.append_path 'spec/integration/rack' 17 | sprockets_env.add_spec_paths_to_sprockets 18 | s.debug = ENV['OPAL_DEBUG'] 19 | } 20 | -------------------------------------------------------------------------------- /diff-lcs/spec/files_to_exclude.txt: -------------------------------------------------------------------------------- 1 | **/ldiff_spec.rb 2 | -------------------------------------------------------------------------------- /diff-lcs/spec/requires.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.add_formatter RSpec::Core::Formatters::JsonFormatter, File.open('/tmp/diff-lcs-results.json', 'w') 3 | config.add_formatter RSpec::Core::Formatters::ProgressFormatter, $stdout 4 | end 5 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'opal-rspec', path: '../' 5 | # gem 'opal', '~> 0.11.0.rc1' 6 | gem 'opal', path: '../../opal' 7 | gem 'opal-sprockets', github: 'opal/opal-sprockets' 8 | 9 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # opal-spec example 2 | 3 | Install dependencies: 4 | 5 | ``` 6 | bundle install 7 | ``` 8 | 9 | Run examples: 10 | 11 | ``` 12 | bundle exec rake 13 | ``` 14 | -------------------------------------------------------------------------------- /example/Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | # Add our opal/ directory to the load path 5 | Opal.append_path File.expand_path('../opal', __FILE__) 6 | 7 | require 'opal/rspec/rake_task' 8 | Opal::RSpec::RakeTask.new(:default) 9 | -------------------------------------------------------------------------------- /example/opal/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | attr_accessor :name 3 | 4 | def initialize(name) 5 | @name = name 6 | end 7 | 8 | def admin? 9 | @name == 'Bob' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /example/spec/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'user' 2 | 3 | describe User do 4 | it '#initialize accepts a name' do 5 | expect(User.new('Jim').name).to eq('Jim') 6 | end 7 | 8 | it 'is an admin if name is Bob' do 9 | expect(User.new('Bob')).to be_admin 10 | end 11 | 12 | it 'is not an admin if name is not Bob' do 13 | expect(User.new('Jim')).to_not be_admin 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /exe/opal-rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Error codes are taken from /usr/include/sysexits.h 4 | 5 | require 'rake' 6 | require 'opal/rspec/runner' 7 | require 'opal/rspec/configuration_parser' 8 | 9 | require 'shellwords' 10 | 11 | parser = Opal::RSpec::Core::Parser.new(ARGV) 12 | options = parser.parse 13 | 14 | case options[:runner] 15 | when String, nil 16 | runner = Opal::RSpec::Runner.new do |server, config| 17 | config.runner = options.delete(:runner) 18 | config.spec_opts = options 19 | end 20 | 21 | runner.run 22 | else 23 | exit options[:runner].(ARGV, $stderr, $stdout) 24 | end 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib-opal/opal-rspec.rb: -------------------------------------------------------------------------------- 1 | require 'opal/rspec' 2 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec.rb: -------------------------------------------------------------------------------- 1 | require 'js' 2 | 3 | require 'opal/rspec/browser_early' if JS[:document] 4 | require 'opal/rspec/pre_require_fixes' 5 | require 'opal/rspec/requires' 6 | require 'opal/rspec/fixes' 7 | require 'opal/rspec/default_config' 8 | require 'opal/rspec/browser' if JS[:document] 9 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async.rb: -------------------------------------------------------------------------------- 1 | require 'await' 2 | 3 | require 'opal/rspec/async/example' 4 | require 'opal/rspec/async/example_group' 5 | require 'opal/rspec/async/memoized_helpers' 6 | require 'opal/rspec/async/hooks' 7 | require 'opal/rspec/async/reporter' 8 | require 'opal/rspec/async/runner' 9 | require 'opal/rspec/async/configuration' 10 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async/configuration.rb: -------------------------------------------------------------------------------- 1 | # await: *await* 2 | 3 | module RSpec 4 | module Core 5 | class Configuration 6 | def with_suite_hooks_await 7 | return yield if dry_run? 8 | 9 | begin 10 | RSpec.current_scope = :before_suite_hook 11 | run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks) 12 | yield.await 13 | ensure 14 | RSpec.current_scope = :after_suite_hook 15 | run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks) 16 | RSpec.current_scope = :suite 17 | end 18 | end 19 | 20 | def run_suite_hooks_await(hook_description, hooks) 21 | context = SuiteHookContext.new(hook_description, reporter) 22 | 23 | hooks.each do |hook| 24 | begin 25 | hook.run(context).await 26 | rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex 27 | context.set_exception(ex) 28 | 29 | # Do not run subsequent `before` hooks if one fails. 30 | # But for `after` hooks, we run them all so that all 31 | # cleanup bits get a chance to complete, minimizing the 32 | # chance that resources get left behind. 33 | break if hooks.equal?(@before_suite_hooks) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async/example.rb: -------------------------------------------------------------------------------- 1 | # await: *await* 2 | 3 | module RSpec 4 | module Core 5 | class Example 6 | def run_await(example_group_instance, reporter) 7 | # added awaits 8 | @example_group_instance = example_group_instance 9 | @reporter = reporter 10 | RSpec.configuration.configure_example(self, hooks) 11 | RSpec.current_example = self 12 | 13 | start(reporter) 14 | Pending.mark_pending!(self, pending) if pending? 15 | 16 | begin 17 | if skipped? 18 | Pending.mark_pending! self, skip 19 | elsif !RSpec.configuration.dry_run? 20 | with_around_and_singleton_context_hooks_await do 21 | begin 22 | run_before_example_await 23 | RSpec.current_scope = :example 24 | @example_group_instance.instance_exec_await(self, &@example_block) 25 | 26 | if pending? 27 | Pending.mark_fixed! self 28 | 29 | raise Pending::PendingExampleFixedError, 30 | 'Expected example to fail since it is pending, but it passed.', 31 | [location] 32 | end 33 | rescue Pending::SkipDeclaredInExample => _ 34 | # The "=> _" is normally useless but on JRuby it is a workaround 35 | # for a bug that prevents us from getting backtraces: 36 | # https://github.com/jruby/jruby/issues/4467 37 | # 38 | # no-op, required metadata has already been set by the `skip` 39 | # method. 40 | rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e 41 | set_exception(e) 42 | ensure 43 | RSpec.current_scope = :after_example_hook 44 | run_after_example_await 45 | end 46 | end 47 | end 48 | rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e 49 | set_exception(e) 50 | ensure 51 | @example_group_instance = nil # if you love something... let it go 52 | end 53 | 54 | finish(reporter) 55 | ensure 56 | execution_result.ensure_timing_set(clock) 57 | RSpec.current_example = nil 58 | end 59 | 60 | def run_before_example_await 61 | # added awaits 62 | @example_group_instance.setup_mocks_for_rspec 63 | hooks.run_await(:before, :example, self) 64 | end 65 | 66 | def run_after_example_await 67 | # added awaits 68 | assign_generated_description if defined?(::RSpec::Matchers) 69 | hooks.run_await(:after, :example, self) 70 | verify_mocks 71 | ensure 72 | @example_group_instance.teardown_mocks_for_rspec 73 | end 74 | 75 | def with_around_and_singleton_context_hooks_await 76 | singleton_context_hooks_host = example_group_instance.singleton_class 77 | singleton_context_hooks_host.run_before_context_hooks_await(example_group_instance) 78 | with_around_example_hooks_await { yield.await } 79 | ensure 80 | singleton_context_hooks_host.run_after_context_hooks_await(example_group_instance) 81 | end 82 | 83 | def with_around_example_hooks_await 84 | RSpec.current_scope = :before_example_hook 85 | hooks.run_await(:around, :example, self) { yield.await } 86 | rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e 87 | set_exception(e) 88 | end 89 | 90 | def instance_exec_await(*args, &block) 91 | @example_group_instance.instance_exec_await(*args, &block) 92 | end 93 | 94 | class Procsy 95 | alias run_await run 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async/example_group.rb: -------------------------------------------------------------------------------- 1 | # await: *await* 2 | 3 | module ::RSpec 4 | module Core 5 | class ExampleGroup 6 | # @param duration [Integer, Float] time in seconds to wait 7 | def delay(duration, &block) 8 | `setTimeout(block, duration * 1000)` 9 | self 10 | end 11 | 12 | def delay_with_promise(duration, &block) 13 | result = PromiseV2.new 14 | delay(duration) { result.resolve } 15 | result.then(&block) 16 | end 17 | 18 | def self.run_await(reporter=RSpec::Core::NullReporter) 19 | # added awaits 20 | return if RSpec.world.wants_to_quit 21 | reporter.example_group_started(self) 22 | 23 | should_run_context_hooks = descendant_filtered_examples.any? 24 | begin 25 | RSpec.current_scope = :before_context_hook 26 | run_before_context_hooks_await(new('before(:context) hook')) if should_run_context_hooks 27 | result_for_this_group = run_examples_await(reporter) 28 | results_for_descendants = ordering_strategy.order(children).map_await { |child| child.run_await(reporter) }.all? 29 | result_for_this_group && results_for_descendants 30 | rescue Pending::SkipDeclaredInExample => ex 31 | for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) } 32 | true 33 | rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex 34 | for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) } 35 | RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met? 36 | false 37 | ensure 38 | RSpec.current_scope = :after_context_hook 39 | run_after_context_hooks_await(new('after(:context) hook')) if should_run_context_hooks 40 | reporter.example_group_finished(self) 41 | end 42 | end 43 | 44 | def self.run_examples_await(reporter) 45 | # added awaits 46 | ordering_strategy.order(filtered_examples).map_await do |example| 47 | next if RSpec.world.wants_to_quit 48 | instance = new(example.inspect_output) 49 | set_ivars(instance, before_context_ivars) 50 | succeeded = example.run_await(instance, reporter) 51 | if !succeeded && reporter.fail_fast_limit_met? 52 | RSpec.world.wants_to_quit = true 53 | end 54 | succeeded 55 | end.all? 56 | end 57 | 58 | # @private 59 | def self.run_before_context_hooks_await(example_group_instance) 60 | set_ivars(example_group_instance, superclass_before_context_ivars) 61 | 62 | @currently_executing_a_context_hook = true 63 | 64 | ContextHookMemoized::Before.isolate_for_context_hook_await(example_group_instance) do 65 | hooks.run_await(:before, :context, example_group_instance) 66 | end 67 | ensure 68 | store_before_context_ivars(example_group_instance) 69 | @currently_executing_a_context_hook = false 70 | end 71 | 72 | def self.run_after_context_hooks_await(example_group_instance) 73 | set_ivars(example_group_instance, before_context_ivars) 74 | 75 | @currently_executing_a_context_hook = true 76 | 77 | ContextHookMemoized::After.isolate_for_context_hook_await(example_group_instance) do 78 | hooks.run_await(:after, :context, example_group_instance) 79 | end 80 | ensure 81 | before_context_ivars.clear 82 | @currently_executing_a_context_hook = false 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async/hooks.rb: -------------------------------------------------------------------------------- 1 | # await: *await* 2 | 3 | require 'rspec/core/hooks' 4 | 5 | module RSpec 6 | module Core 7 | module Hooks 8 | class HookCollections 9 | def run_example_hooks_for_await(example, position, each_method) 10 | # WAS: 11 | # owner_parent_groups.__send__(each_method) do |group| 12 | # group.hooks.run_owned_hooks_for(position, :example, example) 13 | # end 14 | case each_method 15 | when :each 16 | groups = owner_parent_groups 17 | when :reverse_each 18 | groups = owner_parent_groups.reverse 19 | else 20 | raise "Unsupported each_method: #{each_method}" 21 | end 22 | groups.each_await do |group| 23 | group.hooks.run_owned_hooks_for_await(position, :example, example) 24 | end 25 | end 26 | 27 | def run_owned_hooks_for_await(position, scope, example_or_group) 28 | # WAS: 29 | # matching_hooks_for(position, scope, example_or_group).each do |hook| 30 | # hook.run(example_or_group) 31 | # end 32 | 33 | matching_hooks_for(position, scope, example_or_group).each_await do |hook| 34 | hook.run_await(example_or_group) 35 | end 36 | end 37 | 38 | def run_await(position, scope, example_or_group) 39 | return if RSpec.configuration.dry_run? 40 | 41 | if scope == :context 42 | unless example_or_group.class.metadata[:skip] 43 | run_owned_hooks_for_await(position, :context, example_or_group) 44 | end 45 | else 46 | case position 47 | when :before then run_example_hooks_for_await(example_or_group, :before, :reverse_each) 48 | when :after then run_example_hooks_for_await(example_or_group, :after, :each) 49 | when :around then run_around_example_hooks_for_await(example_or_group) { yield.await } 50 | end 51 | end 52 | end 53 | 54 | def run_around_example_hooks_for_await(example) 55 | hooks = FlatMap.flat_map(owner_parent_groups) do |group| 56 | group.hooks.matching_hooks_for(:around, :example, example) 57 | end 58 | 59 | return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy` 60 | 61 | initial_procsy = Example::Procsy.new(example) { yield.await } 62 | hooks.inject(initial_procsy) do |procsy, around_hook| 63 | procsy.wrap { around_hook.execute_with_await(example, procsy) } 64 | end.call.await 65 | end 66 | end 67 | 68 | class BeforeHook < Hook 69 | def run_await(example) 70 | example.instance_exec_await(example, &block) 71 | end 72 | end 73 | 74 | # @private 75 | class AfterHook < Hook 76 | def run_await(example) 77 | example.instance_exec_await(example, &block) 78 | rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex 79 | example.set_exception(ex) 80 | end 81 | end 82 | 83 | # @private 84 | class AfterContextHook < Hook 85 | def run_await(example) 86 | example.instance_exec_await(example, &block) 87 | rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e 88 | RSpec.configuration.reporter.notify_non_example_exception(e, "An error occurred in an `after(:context)` hook.") 89 | end 90 | end 91 | 92 | class AroundHook < Hook 93 | def execute_with_await(example, procsy) 94 | example.instance_exec_await(procsy, &block) 95 | return if procsy.executed? 96 | Pending.mark_skipped!(example, 97 | "#{hook_description} did not execute the example") 98 | end 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async/memoized_helpers.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | # This module is included in {ExampleGroup}, making the methods 4 | # available to be called from within example blocks. 5 | # 6 | # @see ClassMethods 7 | module MemoizedHelpers 8 | class ContextHookMemoized 9 | def self.isolate_for_context_hook_await(example_group_instance) 10 | exploding_memoized = self 11 | 12 | example_group_instance.instance_exec_await do 13 | @__memoized = exploding_memoized 14 | 15 | begin 16 | yield.await 17 | ensure 18 | # This is doing a reset instead of just isolating for context hook. 19 | # Really, this should set the old @__memoized back into place. 20 | # 21 | # Caller is the before and after context hooks 22 | # which are both called from self.run 23 | # I didn't look at why it made tests fail, maybe an object was getting reused in RSpec tests, 24 | # if so, then that probably already works, and its the tests that are wrong. 25 | __init_memoized 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async/reporter.rb: -------------------------------------------------------------------------------- 1 | # await: *await* 2 | 3 | class ::RSpec::Core::Reporter 4 | def report_await(expected_example_count) 5 | # WAS: 6 | # start(expected_example_count) 7 | # begin 8 | # yield self 9 | # ensure 10 | # finish 11 | # end 12 | # NOW: 13 | start(expected_example_count) 14 | begin 15 | yield(self).await 16 | ensure 17 | finish 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/async/runner.rb: -------------------------------------------------------------------------------- 1 | # await: *await* 2 | 3 | module ::RSpec::Core 4 | class Runner 5 | 6 | # Runs the suite of specs and exits the process with an appropriate exit code. 7 | def self.invoke 8 | disable_autorun! 9 | # WAS: 10 | # status = run(ARGV, $stderr, $stdout).to_i 11 | # exit(status) if status != 0 12 | # NOW: 13 | status = run_await(ARGV, $stderr, $stdout).to_i 14 | exit(status) 15 | end 16 | 17 | def self.run_await(args, err=$stderr, out=$stdout) 18 | trap_interrupt 19 | options = ConfigurationOptions.new(args) 20 | 21 | if options.options[:runner] 22 | options.options[:runner].call(options, err, out) 23 | else 24 | new(options).run_await(err, out) 25 | end 26 | end 27 | 28 | def run_await(err, out) 29 | setup(err, out) 30 | return @configuration.reporter.exit_early(exit_code) if RSpec.world.wants_to_quit 31 | 32 | run_specs_await(@world.ordered_example_groups).tap do 33 | persist_example_statuses 34 | end 35 | end 36 | 37 | def run_specs_await(example_groups) 38 | examples_count = @world.example_count(example_groups) 39 | 40 | examples_passed = @configuration.reporter.report_await(examples_count) do |reporter| 41 | @configuration.with_suite_hooks_await do 42 | if examples_count == 0 && @configuration.fail_if_no_examples 43 | return @configuration.failure_exit_code 44 | end 45 | 46 | example_groups.map_await { |g| g.run_await(reporter) }.all? 47 | end 48 | end 49 | 50 | exit_code(examples_passed) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/autorun.rb: -------------------------------------------------------------------------------- 1 | require 'opal' 2 | require 'opal-rspec' 3 | ::RSpec::Core::Runner.autorun 4 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/browser.rb: -------------------------------------------------------------------------------- 1 | require 'opal/rspec/formatter/browser_formatter' 2 | 3 | RSpec.configure do |config| 4 | if OPAL_PLATFORM.nil? 5 | # We want the browser formatter ONLY for the real browser, not 6 | # our headless browser runners. 7 | config.default_formatter = ::Opal::RSpec::BrowserFormatter 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/browser_early.rb: -------------------------------------------------------------------------------- 1 | unless File.respond_to? :read 2 | def File.read(*) 3 | raise Errno::ENOENT 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/default_config.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.default_formatter = ::RSpec::Core::Formatters::ProgressFormatter 3 | 4 | # # Have to do this in 2 places. This will ensure the default formatter gets the right IO, but need to do this here for custom formatters 5 | # # that will be constructed BEFORE Runner.autorun runs (see runner.rb) 6 | # config.output_stream = $stdout 7 | 8 | # This shouldn't be in here, but RSpec uses undef to change this configuration and that doesn't work well enough yet 9 | config.expect_with :rspec do |c| 10 | c.syntax = [:should, :expect] 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes.rb: -------------------------------------------------------------------------------- 1 | require 'encoding' unless Object.const_defined? :Encoding 2 | # Thread usage in core.rb 3 | require 'thread' 4 | require_relative 'fixes/diff-lcs' 5 | require_relative 'fixes/rspec' 6 | require_relative 'fixes/opal' 7 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/diff-lcs.rb: -------------------------------------------------------------------------------- 1 | require_relative 'diff-lcs/lcs' 2 | require_relative 'diff-lcs/hunk' 3 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/diff-lcs/hunk.rb: -------------------------------------------------------------------------------- 1 | require 'diff/lcs/hunk' 2 | 3 | class Diff::LCS::Hunk 4 | 5 | def unified_diff(last = false) 6 | # Calculate item number range. 7 | s = encode("@@ -#{unified_range(:old, last)} +#{unified_range(:new, last)} @@\n") 8 | 9 | # Outlist starts containing the hunk of the old file. Removing an item 10 | # just means putting a '-' in front of it. Inserting an item requires 11 | # getting it from the new file and splicing it in. We splice in 12 | # +num_added+ items. Remove blocks use +num_added+ because splicing 13 | # changed the length of outlist. 14 | # 15 | # We remove +num_removed+ items. Insert blocks use +num_removed+ 16 | # because their item numbers -- corresponding to positions in the NEW 17 | # file -- don't take removed items into account. 18 | lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0 19 | 20 | # standard:disable Performance/UnfreezeString 21 | outlist = @data_old[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") } 22 | # standard:enable Performance/UnfreezeString 23 | 24 | last_block = blocks[-1] 25 | 26 | if last 27 | old_missing_newline = missing_last_newline?(@data_old) 28 | new_missing_newline = missing_last_newline?(@data_new) 29 | end 30 | 31 | @blocks.each do |block| 32 | block.remove.each do |item| 33 | op = item.action.to_s # - 34 | offset = item.position - lo + num_added 35 | # fixed for Opal: 36 | outlist[offset] = encode(op) + outlist[offset][1..-1] 37 | num_removed += 1 38 | end 39 | 40 | if last && block == last_block && old_missing_newline && !new_missing_newline 41 | outlist << encode('\\ No newline at end of file') 42 | num_removed += 1 43 | end 44 | 45 | block.insert.each do |item| 46 | op = item.action.to_s # + 47 | offset = item.position - @start_new + num_removed 48 | outlist[offset, 0] = encode(op) + @data_new[item.position].chomp 49 | num_added += 1 50 | end 51 | end 52 | 53 | outlist << encode('\\ No newline at end of file') if last && new_missing_newline 54 | 55 | s += outlist.join(encode("\n")) 56 | 57 | s 58 | end 59 | 60 | def context_diff(last = false) 61 | s = encode("***************\n") 62 | s += encode("*** #{context_range(:old, ",", last)} ****\n") 63 | r = context_range(:new, ",", last) 64 | 65 | if last 66 | old_missing_newline = missing_last_newline?(@data_old) 67 | new_missing_newline = missing_last_newline?(@data_new) 68 | end 69 | 70 | # Print out file 1 part for each block in context diff format if there 71 | # are any blocks that remove items 72 | lo, hi = @start_old, @end_old 73 | removes = @blocks.reject { |e| e.remove.empty? } 74 | 75 | unless removes.empty? 76 | # standard:disable Performance/UnfreezeString 77 | outlist = @data_old[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") } 78 | # standard:enable Performance/UnfreezeString 79 | 80 | last_block = removes[-1] 81 | 82 | removes.each do |block| 83 | block.remove.each do |item| 84 | outlist[item.position - lo] = encode(block.op) + outlist[item.position - lo][1..-1] # - or ! 85 | end 86 | 87 | if last && block == last_block && old_missing_newline 88 | outlist << encode('\\ No newline at end of file') 89 | end 90 | end 91 | 92 | s += outlist.join(encode("\n")) + encode("\n") 93 | end 94 | 95 | s += encode("--- #{r} ----\n") 96 | lo, hi = @start_new, @end_new 97 | inserts = @blocks.reject { |e| e.insert.empty? } 98 | 99 | unless inserts.empty? 100 | # standard:disable Performance/UnfreezeString 101 | outlist = @data_new[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") } 102 | # standard:enable Performance/UnfreezeString 103 | 104 | last_block = inserts[-1] 105 | 106 | inserts.each do |block| 107 | block.insert.each do |item| 108 | outlist[item.position - lo] = encode(block.op) + outlist[item.position - lo][1..-1] # + or ! 109 | end 110 | 111 | if last && block == last_block && new_missing_newline 112 | outlist << encode('\\ No newline at end of file') 113 | end 114 | end 115 | s += outlist.join(encode("\n")) 116 | end 117 | 118 | s 119 | end 120 | 121 | def old_diff(_last = false) 122 | warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1 123 | 124 | block = @blocks[0] 125 | 126 | # Calculate item number range. Old diff range is just like a context 127 | # diff range, except the ranges are on one line with the action between 128 | # them. 129 | s = encode("#{context_range(:old, ",")}#{OLD_DIFF_OP_ACTION[block.op]}#{context_range(:new, ",")}\n") 130 | # If removing anything, just print out all the remove lines in the hunk 131 | # which is just all the remove lines in the block. 132 | unless block.remove.empty? 133 | @data_old[@start_old..@end_old].each { |e| s += encode("< ") + e.chomp + encode("\n") } 134 | end 135 | 136 | s += encode("---\n") if block.op == "!" 137 | 138 | unless block.insert.empty? 139 | @data_new[@start_new..@end_new].each { |e| s += encode("> ") + e.chomp + encode("\n") } 140 | end 141 | 142 | s 143 | end 144 | 145 | 146 | def ed_diff(format, _last = false) 147 | warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1 148 | 149 | s = 150 | if format == :reverse_ed 151 | encode("#{ED_DIFF_OP_ACTION[@blocks[0].op]}#{context_range(:old, ",")}\n") 152 | else 153 | encode("#{context_range(:old, " ")}#{ED_DIFF_OP_ACTION[@blocks[0].op]}\n") 154 | end 155 | 156 | unless @blocks[0].insert.empty? 157 | @data_new[@start_new..@end_new].each do |e| 158 | s += e.chomp + encode("\n") 159 | end 160 | s += encode(".\n") 161 | end 162 | s 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/diff-lcs/lcs.rb: -------------------------------------------------------------------------------- 1 | require 'diff/lcs' 2 | 3 | class << Diff::LCS 4 | # mutable strings 5 | def patch(src, patchset, direction = nil) 6 | # Normalize the patchset. 7 | has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset) 8 | 9 | return src.respond_to?(:dup) ? src.dup : src unless has_changes 10 | 11 | string = src.is_a?(String) 12 | # Start with a new empty type of the source's class 13 | res = src.class.new 14 | 15 | res = [] if string 16 | 17 | direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset) 18 | 19 | ai = bj = 0 20 | 21 | patch_map = PATCH_MAP[direction] 22 | 23 | patchset.each do |change| 24 | # Both Change and ContextChange support #action 25 | action = patch_map[change.action] 26 | 27 | case change 28 | when Diff::LCS::ContextChange 29 | case direction 30 | when :patch 31 | el = change.new_element 32 | op = change.old_position 33 | np = change.new_position 34 | when :unpatch 35 | el = change.old_element 36 | op = change.new_position 37 | np = change.old_position 38 | end 39 | 40 | case action 41 | when "-" # Remove details from the old string 42 | while ai < op 43 | res << (string ? src[ai, 1] : src[ai]) 44 | ai += 1 45 | bj += 1 46 | end 47 | ai += 1 48 | when "+" 49 | while bj < np 50 | res << (string ? src[ai, 1] : src[ai]) 51 | ai += 1 52 | bj += 1 53 | end 54 | 55 | res << el 56 | bj += 1 57 | when "=" 58 | # This only appears in sdiff output with the SDiff callback. 59 | # Therefore, we only need to worry about dealing with a single 60 | # element. 61 | res << el 62 | 63 | ai += 1 64 | bj += 1 65 | when "!" 66 | while ai < op 67 | res << (string ? src[ai, 1] : src[ai]) 68 | ai += 1 69 | bj += 1 70 | end 71 | 72 | bj += 1 73 | ai += 1 74 | 75 | res << el 76 | end 77 | when Diff::LCS::Change 78 | case action 79 | when "-" 80 | while ai < change.position 81 | res << (string ? src[ai, 1] : src[ai]) 82 | ai += 1 83 | bj += 1 84 | end 85 | ai += 1 86 | when "+" 87 | while bj < change.position 88 | res << (string ? src[ai, 1] : src[ai]) 89 | ai += 1 90 | bj += 1 91 | end 92 | 93 | bj += 1 94 | 95 | res << change.element 96 | end 97 | end 98 | end 99 | 100 | while ai < src.size 101 | res << (string ? src[ai, 1] : src[ai]) 102 | ai += 1 103 | bj += 1 104 | end 105 | 106 | if string 107 | res.join 108 | else 109 | res 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/opal.rb: -------------------------------------------------------------------------------- 1 | require 'opal/platform' 2 | require 'opal-parser' 3 | require 'thread' 4 | require 'corelib/marshal' 5 | require 'ruby2_keywords' 6 | 7 | class IO 8 | def closed? 9 | true 10 | end 11 | end 12 | 13 | Errno::ENOTDIR = Class.new(SystemCallError) 14 | 15 | require 'nodejs' if OPAL_PLATFORM == 'nodejs' 16 | 17 | module Kernel 18 | def trap(sig, &block) 19 | end 20 | end 21 | 22 | require 'js' 23 | 24 | module Opal 25 | module RSpec 26 | module Compatibility 27 | module ModuleCase 28 | end 29 | 30 | module ModuleCase2 31 | include ModuleCase 32 | end 33 | 34 | class ModuleCase3 35 | include ModuleCase2 36 | end 37 | 38 | # not currently needed but is referenced in space.rb fix, https://github.com/opal/opal/issues/1279 - fixed in 0.10 39 | def self.module_case_works_right? 40 | instance = ModuleCase3.new 41 | ModuleCase === instance && instance.kind_of?(ModuleCase) 42 | end 43 | 44 | module MultModSuper1 45 | def stuff 46 | :howdy 47 | end 48 | end 49 | 50 | module MultModSuper2 51 | def stuff 52 | super 53 | end 54 | end 55 | 56 | module MultModSuper3 57 | include MultModSuper1 58 | include MultModSuper2 59 | end 60 | 61 | class MultModSuperClass 62 | include MultModSuper3 63 | end 64 | 65 | # https://github.com/opal/opal/issues/568 - still not fixed 66 | def self.multiple_module_include_super_works_right? 67 | MultModSuperClass.new.stuff == :howdy 68 | rescue Exception => _ 69 | false 70 | end 71 | end 72 | end 73 | end 74 | 75 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'rspec/core' 2 | require_relative 'rspec/support' 3 | require_relative 'rspec/expectations' 4 | require_relative 'rspec/matchers' 5 | require_relative 'rspec/mocks' 6 | require_relative 'rspec/example_groups' 7 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core.rb: -------------------------------------------------------------------------------- 1 | require_relative 'core/formatters' 2 | require_relative 'core/notifications' 3 | require_relative 'core/ordering' 4 | require_relative 'core/metadata' 5 | require_relative 'core/configuration' 6 | require_relative 'core/example_status_persister' 7 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/configuration.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | require 'rspec/core/configuration' 4 | 5 | module ::RSpec; module Core; class Configuration 6 | def files_or_directories_to_run=(*files) 7 | files = files.flatten 8 | 9 | # patch: rspec -> opal-rspec 10 | if (command == 'opal-rspec' || Runner.running_in_drb?) && default_path && files.empty? 11 | files << default_path 12 | end 13 | 14 | @files_or_directories_to_run = files 15 | @files_to_run = nil 16 | end 17 | 18 | def requires=(paths) 19 | # can't change requires @ this stage, this method calls RubyProject which will crash on Opal 20 | end 21 | 22 | def remove_ruby_ext(str) 23 | str.gsub(/(?:\.js)?\.(?:rb|opal|\{rb,opal\})\z/, '') 24 | end 25 | 26 | def glob_to_re_expand_alternatives(glob) 27 | # If there are no braces, just return the string as an array 28 | return [glob] unless glob =~ /(.*)\{([^\{\}]*?)\}(.*)/ 29 | 30 | prefix, contents, suffix = $1, $2, $3 31 | 32 | alternatives = contents.split(',') 33 | 34 | expanded_patterns = alternatives.map do |alternative| 35 | "#{prefix}#{alternative}#{suffix}" 36 | end 37 | 38 | # Recursively expand for the rest of the pattern 39 | return expanded_patterns.flat_map { |pattern| glob_to_re_expand_alternatives(pattern) } 40 | end 41 | 42 | def glob_to_re(path, pattern) 43 | pattern = remove_ruby_ext(pattern) 44 | if pattern.start_with?(path) 45 | path = "" 46 | else 47 | path += "/" unless path.end_with?("/") 48 | end 49 | pattern = path + pattern 50 | patterns = glob_to_re_expand_alternatives(pattern) 51 | re = patterns.map { |i| Regexp.escape(i) }.join("|").then { |i| "(?:#{i})" } 52 | re = re.gsub('\/\*\*\/', '(?:/|/.*?/)') 53 | .gsub('\*', '[^/]*?') 54 | .gsub('\?', '[^/]') 55 | re = '(?:^|/)' + re + "$" 56 | # Strip multiple '/'s 57 | re = re.gsub(%r{(\\/|/)+}, '/') 58 | # Strip the `/./` 59 | re = re.gsub('/\./', '/') 60 | re = re.gsub('(?:^|/)\./', '(?:^|/)') 61 | Regexp.new(re) 62 | end 63 | 64 | # Only load from loaded files 65 | def get_matching_files(path, pattern) 66 | if pattern.is_a?(Array) 67 | return pattern.map { |pat| get_matching_files(path, pat) }.flatten.sort.uniq 68 | end 69 | `Object.keys(Opal.modules)`.grep(glob_to_re(path, pattern)).sort 70 | end 71 | 72 | # A crude logic to check if a path is a directory perhaps... 73 | # This ought to work in places where we don't have a filesystem. 74 | def is_directory?(path) 75 | return true if path.end_with? '/' 76 | # This is passed with ":" if we run something like: 77 | # opal-rspec spec-opal-passing/tautology_spec.rb:8 78 | return false if path =~ /\[[0-9:]+\]$|:[0-9]+$/ 79 | # Ruby files are certainly not directories 80 | return false if ['.rb', '.opal'].any? { |i| path.end_with? i } 81 | # Otherwise, let's check for modules 82 | !`Object.keys(Opal.modules)`.any? { |i| i.end_with?("/"+remove_ruby_ext(path)) } 83 | end 84 | 85 | def get_files_to_run(paths) 86 | files = paths_to_check(paths).flat_map do |path| 87 | path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR 88 | is_directory?(path) ? gather_directories(path) : extract_location(path) 89 | end.uniq 90 | 91 | return files unless only_failures? 92 | relative_files = files.map { |f| Metadata.relative_path(File.expand_path f) } 93 | intersection = (relative_files & spec_files_with_failures.to_a) 94 | intersection.empty? ? files : intersection 95 | end 96 | 97 | def opal_special_load(file) 98 | file = remove_ruby_ext(file) 99 | long_file = `Object.keys(Opal.modules)`.find { |i| i.end_with?(file) } 100 | `Opal.modules[file] = Opal.modules[long_file]` if long_file 101 | 102 | # Let's try a normalized load 103 | `Opal.load_normalized(file)` 104 | rescue LoadError 105 | # Otherwise, a regular require 106 | require file 107 | end 108 | 109 | alias load_file_handling_errors_before_opal load_file_handling_errors 110 | 111 | def load_file_handling_errors(method, file) 112 | load_file_handling_errors_before_opal(:opal_special_load, file) 113 | end 114 | end; end; end 115 | 116 | class ::RSpec::Core::ConfigurationOptions 117 | # Opal-RSpec should work without need of filesystem 118 | # access, therefore we can't support access to the 119 | # options file. 120 | def options_file_as_erb_string(path) 121 | # ERB.new(File.read(path), nil, '-').result(binding) 122 | # ERB.new(File.read(path), nil, '-') 123 | '' 124 | end 125 | 126 | # Pass command line options directly 127 | def command_line_options 128 | $rspec_opts || {} 129 | end 130 | end 131 | 132 | # Set the default path to spec-opal, to be overwritten 133 | # later. 134 | RSpec.configuration.default_path = "spec-opal" 135 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/example_status_persister.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | # Persists example ids and their statuses so that we can filter 4 | # to just the ones that failed the last time they ran. 5 | # @private 6 | class ExampleStatusPersister 7 | def persist 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/formatters.rb: -------------------------------------------------------------------------------- 1 | require_relative 'formatters/deprecation_formatter' 2 | require_relative 'formatters/loader' 3 | require_relative 'formatters/exception_presenter' 4 | require_relative 'formatters/syntax_highlighter' 5 | require_relative 'formatters/snippet_extractor' 6 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/formatters/deprecation_formatter.rb: -------------------------------------------------------------------------------- 1 | class RSpec::Core::Formatters::DeprecationFormatter 2 | # GeneratedDeprecationMessage is a Struct 3 | GeneratedDeprecationMessage.class_eval do 4 | def to_s 5 | msg = String.new("#{@data.deprecated} is deprecated.") 6 | msg += " Use #{@data.replacement} instead." if @data.replacement 7 | msg += " Called from #{@data.call_site}." if @data.call_site 8 | msg 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/formatters/exception_presenter.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | module Formatters 4 | class ExceptionPresenter 5 | def indent_lines(lines, failure_number) 6 | alignment_basis = ' ' * @indentation 7 | alignment_basis += "#{failure_number}) " if failure_number 8 | indentation = ' ' * alignment_basis.length 9 | 10 | lines.each_with_index.map do |line, index| 11 | if index == 0 12 | "#{alignment_basis}#{line}" 13 | elsif line.empty? 14 | line 15 | else 16 | "#{indentation}#{line}" 17 | end 18 | end 19 | end 20 | 21 | def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes) 22 | lines = fully_formatted_lines(failure_number, colorizer) 23 | lines.join("\n") + "\n" 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/formatters/loader.rb: -------------------------------------------------------------------------------- 1 | class ::RSpec::Core::Formatters::Loader 2 | def underscore(camel_cased_word) 3 | # string mutation 4 | word = camel_cased_word.to_s.dup 5 | word = word.gsub(/::/, '/') 6 | word = word.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') 7 | word = word.gsub(/([a-z\d])([A-Z])/, '\1_\2') 8 | word = word.tr("-", "_") 9 | word.downcase 10 | end 11 | end 12 | 13 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/formatters/snippet_extractor.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | module Formatters 4 | # @private 5 | class SnippetExtractor 6 | def self.source_from_file(path) 7 | # Don't check for file existence, we may still have its embedded source 8 | # raise NoSuchFileError unless File.exist?(path) 9 | RSpec.world.source_from_file(path) 10 | rescue Errno::ENOENT, Errno::ENAMETOOLONG 11 | # But if we really don't, well... 12 | raise NoSuchFileError 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/formatters/syntax_highlighter.rb: -------------------------------------------------------------------------------- 1 | require 'opal-replutils' 2 | 3 | module RSpec 4 | module Core 5 | module Formatters 6 | # @private 7 | # Provides terminal syntax highlighting of code snippets 8 | # when coderay is available. 9 | class SyntaxHighlighter 10 | # A poor-man highlighter 11 | def highlight(lines) 12 | REPLUtils::ColorPrinter.colorize(lines.join("\n")).split("\n") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/metadata.rb: -------------------------------------------------------------------------------- 1 | module ::RSpec::Core::Metadata 2 | class HashPopulator 3 | def populate_location_attributes 4 | backtrace = user_metadata.delete(:caller) 5 | 6 | file_path, line_number = if backtrace 7 | file_path_and_line_number_from(backtrace) 8 | elsif block.respond_to?(:source_location) 9 | block.source_location 10 | else 11 | file_path_and_line_number_from(caller) 12 | end 13 | 14 | file_path ||= "" 15 | 16 | relative_file_path = ::RSpec::Core::Metadata.relative_path(file_path) 17 | absolute_file_path = File.expand_path(relative_file_path) 18 | metadata[:file_path] = relative_file_path 19 | metadata[:line_number] = line_number.to_i 20 | metadata[:location] = "#{relative_file_path}:#{line_number}" 21 | metadata[:absolute_file_path] = absolute_file_path 22 | metadata[:rerun_file_path] ||= relative_file_path 23 | metadata[:scoped_id] = build_scoped_id_for(absolute_file_path) 24 | end 25 | 26 | def build_description_from(parent_description=nil, my_description=nil) 27 | return parent_description.to_s unless my_description 28 | return my_description.to_s if parent_description.to_s == '' 29 | separator = description_separator(parent_description, my_description) 30 | # WAS: 31 | # (parent_description.to_s + separator) << my_description.to_s 32 | # NOW: 33 | (parent_description.to_s + separator) + my_description.to_s 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/notifications.rb: -------------------------------------------------------------------------------- 1 | require_relative 'notifications/examples_notification' 2 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/notifications/examples_notification.rb: -------------------------------------------------------------------------------- 1 | module ::RSpec::Core::Notifications 2 | class ExamplesNotification 3 | def fully_formatted_pending_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes) 4 | formatted = "\nPending: (Failures listed here are expected and do not affect your suite's status)\n".dup 5 | 6 | pending_notifications.each_with_index do |notification, index| 7 | formatted += notification.fully_formatted(index.next, colorizer) 8 | end 9 | 10 | formatted 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/ordering.rb: -------------------------------------------------------------------------------- 1 | require_relative 'ordering/random' 2 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/core/ordering/random.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | # Random causes problems that can lock up a browser (see README) 4 | class ::RSpec::Core::Ordering::Random 5 | HIDE_RANDOM_WARNINGS = false 6 | 7 | def initialize(configuration) 8 | `console.warn("Random order is not currently supported by opal-rspec, using default order.")` unless HIDE_RANDOM_WARNINGS 9 | end 10 | 11 | # Identity is usually the default, so borrowing its implementation, this forces 'accidental' random usage down that path 12 | def order(items) 13 | items 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/example_groups.rb: -------------------------------------------------------------------------------- 1 | module RSpec::ExampleGroups 2 | # opal cannot use mutable strings AND opal doesnt support `\A` or `\z` anchors 3 | def self.base_name_for(group) 4 | return "Anonymous" if group.description.empty? 5 | 6 | # convert to CamelCase 7 | name = ' ' + group.description 8 | 9 | # replaced gsub! with name = name.gsub (mutable strings) 10 | name = name.gsub(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) { Regexp.last_match[1].upcase } 11 | 12 | # mutable strings on these 2 13 | name = name.lstrip # Remove leading whitespace 14 | name = name.gsub(/\W/, '') # JRuby, RBX and others don't like non-ascii in const names 15 | 16 | # Ruby requires first const letter to be A-Z. Use `Nested` 17 | # as necessary to enforce that. 18 | # name.gsub!(/\A([^A-Z]|\z)/, 'Nested\1') 19 | # opal-rspec, mutable strings, also substituted in ^ for \A since \A and $ for \z is not supported in JS regex 20 | name = name.gsub(/^([^A-Z]|$)/, 'Nested\1') 21 | 22 | name 23 | end 24 | 25 | # opal cannot use mutable strings 26 | def self.disambiguate(name, const_scope) 27 | return name unless const_defined_on?(const_scope, name) 28 | 29 | # Add a trailing number if needed to disambiguate from an existing constant. 30 | name = name + "_2" 31 | 32 | while const_defined_on?(const_scope, name) 33 | name = name.next 34 | end 35 | 36 | name 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/expectations.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opal/opal-rspec/8acd492c077617632b5bbbb1f1290e3647b1a8a0/lib-opal/opal/rspec/fixes/rspec/expectations.rb -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/matchers.rb: -------------------------------------------------------------------------------- 1 | require_relative 'matchers/built_in' 2 | require_relative 'matchers/expecteds_for_multiple_diffs' 3 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/matchers/built_in.rb: -------------------------------------------------------------------------------- 1 | require_relative 'built_in/base_matcher' 2 | require_relative 'built_in/start_and_end_with' 3 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/matchers/built_in/base_matcher.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/matchers/built_in/base_matcher' 2 | 3 | module RSpec 4 | module Matchers 5 | module BuiltIn 6 | class BaseMatcher 7 | # activesupport/lib/active_support/inflector/methods.rb, line 48 8 | # mutable strings fixed for opal 9 | def self.underscore(camel_cased_word) 10 | word = camel_cased_word.to_s.dup 11 | word = word.gsub(/::/, '/') 12 | word = word.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') 13 | word = word.gsub(/([a-z\d])([A-Z])/, '\1_\2') 14 | word = word.tr("-", "_") 15 | word.downcase 16 | end 17 | 18 | def description 19 | desc = EnglishPhrasing.split_words(self.class.matcher_name) 20 | desc += EnglishPhrasing.list(@expected) if defined?(@expected) 21 | desc 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/matchers/built_in/start_and_end_with.rb: -------------------------------------------------------------------------------- 1 | module ::RSpec::Matchers::BuiltIn 2 | class StartAndEndWith 3 | def failure_message 4 | msg = super 5 | if @actual_does_not_have_ordered_elements 6 | msg += ", but it does not have ordered elements" 7 | elsif !actual.respond_to?(:[]) 8 | msg += ", but it cannot be indexed using #[]" 9 | end 10 | msg 11 | # string mutation 12 | # super.tap do |msg| 13 | # if @actual_does_not_have_ordered_elements 14 | # msg << ", but it does not have ordered elements" 15 | # elsif !actual.respond_to?(:[]) 16 | # msg << ", but it cannot be indexed using #[]" 17 | # end 18 | # end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/matchers/expecteds_for_multiple_diffs.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | # @api private 4 | # Handles list of expected values when there is a need to render 5 | # multiple diffs. Also can handle one value. 6 | class ExpectedsForMultipleDiffs 7 | def self.truncated(description) 8 | return description if description.length <= DESCRIPTION_MAX_LENGTH 9 | description[0...DESCRIPTION_MAX_LENGTH - 3] + "..." 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/mocks.rb: -------------------------------------------------------------------------------- 1 | require_relative 'mocks/error_generator' 2 | require_relative 'mocks/proxy' 3 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/mocks/error_generator.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | class ErrorGenerator 4 | # mutable strings 5 | def error_message(expectation, args_for_multiple_calls) 6 | expected_args = format_args(expectation.expected_args) 7 | actual_args = format_received_args(args_for_multiple_calls) 8 | 9 | if RSpec::Support::RubyFeatures.distincts_kw_args_from_positional_hash? && expected_args == actual_args 10 | expected_hash = expectation.expected_args.last 11 | actual_hash = args_for_multiple_calls.last.last 12 | if Hash === expected_hash && Hash === actual_hash && 13 | (Hash.ruby2_keywords_hash?(expected_hash) != Hash.ruby2_keywords_hash?(actual_hash)) 14 | actual_args += Hash.ruby2_keywords_hash?(actual_hash) ? " (keyword arguments)" : " (options hash)" 15 | expected_args += Hash.ruby2_keywords_hash?(expected_hash) ? " (keyword arguments)" : " (options hash)" 16 | end 17 | end 18 | 19 | message = default_error_message(expectation, expected_args, actual_args) 20 | 21 | if args_for_multiple_calls.one? 22 | diff = diff_message(expectation.expected_args, args_for_multiple_calls.first) 23 | message += "\nDiff:#{diff}" unless diff.strip.empty? 24 | end 25 | 26 | message 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/mocks/proxy.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | # @private 4 | class Proxy 5 | def ensure_can_be_proxied!(object) 6 | return unless object.is_a?(Symbol) || object.frozen? 7 | return if object.nil? 8 | 9 | msg = "Cannot proxy frozen objects" 10 | if Symbol === object 11 | msg += ". Symbols such as #{object} cannot be mocked or stubbed." 12 | else 13 | msg += ", rspec-mocks relies on proxies for method stubbing and expectations." 14 | end 15 | raise ArgumentError, msg 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/support.rb: -------------------------------------------------------------------------------- 1 | require_relative 'support/encoded_string' 2 | require_relative 'support/formatter_support' 3 | require_relative 'support/differ' 4 | require_relative 'support/ruby_features' 5 | require_relative 'support/source' 6 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/support/differ.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Support 3 | class Differ 4 | def hash_to_string(hash) 5 | formatted_hash = ObjectFormatter.prepare_for_inspection(hash) 6 | formatted_hash.keys.sort_by { |k| k.to_s }.map do |key| 7 | pp_key = PP.singleline_pp(key, []) 8 | pp_value = PP.singleline_pp(formatted_hash[key], []) 9 | 10 | "#{pp_key.join} => #{pp_value.join}," 11 | end.join("\n") 12 | end 13 | 14 | def object_to_string(object) 15 | object = @object_preparer.call(object) 16 | case object 17 | when Hash 18 | hash_to_string(object) 19 | when Array 20 | PP.pp(ObjectFormatter.prepare_for_inspection(object), []).join 21 | when String 22 | object =~ /\n/ ? object : object.inspect 23 | else 24 | PP.pp(object, []).join 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/support/encoded_string.rb: -------------------------------------------------------------------------------- 1 | class Encoding::UndefinedConversionError < StandardError; end unless defined? Encoding::UndefinedConversionError 2 | class Encoding::InvalidByteSequenceError < StandardError; end unless defined? Encoding::InvalidByteSequenceError 3 | class Encoding::ConverterNotFoundError < StandardError; end unless defined? Encoding::ConverterNotFoundError 4 | 5 | # Opal doesn't support encoding 6 | class RSpec::Support::EncodedString 7 | def <<(string) 8 | @string += matching_encoding(string) 9 | end 10 | 11 | def self.pick_encoding(source_a, source_b) 12 | "utf-8" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/support/formatter_support.rb: -------------------------------------------------------------------------------- 1 | module FormatterSupport 2 | def run_example_specs_with_formatter(formatter_option) 3 | options = RSpec::Core::ConfigurationOptions.new(%W[spec/rspec/core/resources/formatter_specs.rb --format #{formatter_option} --order defined]) 4 | 5 | err, out = StringIO.new, StringIO.new 6 | err.set_encoding("utf-8") if err.respond_to?(:set_encoding) 7 | 8 | runner = RSpec::Core::Runner.new(options) 9 | configuration = runner.instance_variable_get("@configuration") 10 | configuration.backtrace_formatter.exclusion_patterns << /rspec_with_simplecov/ 11 | configuration.backtrace_formatter.inclusion_patterns = [] 12 | 13 | runner.run(err, out) 14 | 15 | # WAS: 16 | # output = out.string 17 | # output.gsub!(/\d+(?:\.\d+)?(s| seconds)/, "n.nnnn\\1") 18 | # NOW: 19 | output = out.string.gsub(/\d+(?:\.\d+)?(s| seconds)/, "n.nnnn\\1") 20 | 21 | caller_line = RSpec::Core::Metadata.relative_path(caller.first) 22 | output.lines.reject do |line| 23 | # remove the direct caller as that line is different for the summary output backtraces 24 | line.include?(caller_line) || 25 | 26 | # ignore scirpt/rspec_with_simplecov because we don't usually have it locally but 27 | # do have it on travis 28 | line.include?("script/rspec_with_simplecov") || 29 | 30 | # this line varies a bit depending on how you run the specs (via `rake` vs `rspec`) 31 | line.include?('/exe/rspec:') 32 | end.join 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/support/ruby_features.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Support 3 | # Temporary implementation 4 | def self.require_rspec_support(what) 5 | require "rspec/support/#{what}" 6 | end 7 | end 8 | end 9 | 10 | require 'rspec/support/ruby_features' 11 | 12 | module RSpec 13 | module Support 14 | module RubyFeatures 15 | module_function 16 | 17 | def ripper_supported? 18 | false 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/fixes/rspec/support/source.rb: -------------------------------------------------------------------------------- 1 | require 'js' 2 | 3 | module RSpec 4 | module Support 5 | class Source 6 | # Allow to use embedded sources 7 | def self.from_file(path) 8 | source = JS[:Opal].JS[:file_sources].JS[path] 9 | source ||= JS[:Opal].JS[:file_sources].JS["./#{path}"] 10 | source ||= File.read(path) 11 | new(source, path) 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib-opal/opal/rspec/formatter/browser_formatter.rb: -------------------------------------------------------------------------------- 1 | require_relative 'document_io' 2 | require_relative 'html_printer' 3 | 4 | module Opal 5 | module RSpec 6 | class BrowserFormatter < ::RSpec::Core::Formatters::HtmlFormatter 7 | ::RSpec::Core::Formatters.register self, :example_group_finished 8 | 9 | def initialize(output) 10 | super DocumentIO.new 11 | @printer = Opal::RSpec::HtmlPrinter.new(@output) 12 | end 13 | 14 | def example_group_started(notification) 15 | # Since we hook print_example_group_end, we override this method 16 | @example_group_red = false 17 | @example_group_number += 1 18 | 19 | @printer.print_example_group_start(example_group_number, notification.group.description, notification.group.parent_groups.size) 20 | @printer.flush 21 | end 22 | 23 | def example_group_finished(notification) 24 | @printer.print_example_group_end 25 | end 26 | 27 | def start_dump(_notification) 28 | # Don't need to call "print_example_group_end" like base does since we hook that event 29 | end 30 | 31 | def extra_failure_content(failure) 32 | backtrace = failure.exception.backtrace.map { |line| ::RSpec.configuration.backtrace_formatter.backtrace_line(line) } 33 | # No snippet extractor due to code ray dependency 34 | "
#{backtrace.compact}
"
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib-opal/opal/rspec/formatter/document_io.rb:
--------------------------------------------------------------------------------
1 | # backtick_javascript: true
2 |
3 | module Opal
4 | module RSpec
5 | class DocumentIO < IO
6 | include IO::Writable if defined? IO::Writable
7 |
8 | def initialize
9 | `document.open()`
10 | end
11 |
12 | def close
13 | @closed = true
14 | `document.close()`
15 | end
16 |
17 | def write(html)
18 | if @closed
19 | `console.error(#{"DOC closed, can't write #{html}" })`
20 | else
21 | `document.write(#{html})`
22 | end
23 | end
24 |
25 | def flush
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib-opal/opal/rspec/formatter/element.rb:
--------------------------------------------------------------------------------
1 | # backtick_javascript: true
2 |
3 | module Opal
4 | module RSpec
5 | class Element
6 | attr_reader :native
7 |
8 | def self.id(id)
9 | new(`document.getElementById(id)`)
10 | end
11 |
12 | def self.klass(klass)
13 | new(`document.getElementsByClassName(#{klass})[0]`)
14 | end
15 |
16 | def self.from_string(str)
17 | dummy_div = `document.createElement('div')`
18 | `#{dummy_div}.innerHTML = #{str}`
19 | new(`#{dummy_div}.children[0]`)
20 | end
21 |
22 | def initialize(el, attrs={})
23 | if String === el
24 | @native = `document.createElement(el)`
25 | else
26 | @native = el
27 | end
28 |
29 | attrs.each { |name, val| __send__ "#{name}=", val }
30 | end
31 |
32 | def class_name
33 | `#@native.className`
34 | end
35 |
36 | def get_child_by_tag_name(tag, index=0)
37 | elements = `#@native.getElementsByTagName(#{tag})`
38 | # is an HTMLCollection, not an array
39 | element_array = []
40 | %x{
41 | for (var i=0; i < #{elements}.length; i++) {
42 | #{element_array}.push(#{elements}[i]);
43 | }
44 | }
45 | Element.new(element_array[index])
46 | end
47 |
48 | def class_name=(name)
49 | `#@native.className = #{name}`
50 | end
51 |
52 | def native
53 | `#@native`
54 | end
55 |
56 | def outer_html
57 | `#@native.outerHTML`
58 | end
59 |
60 | def on_click=(lambda)
61 | `#@native.onclick = #{lambda}`
62 | end
63 |
64 | def html=(html)
65 | `#@native.innerHTML = #{html}`
66 | end
67 |
68 | def text=(text)
69 | self.html = text.gsub(/, '<').gsub(/>/, '>')
70 | end
71 |
72 | def type=(type)
73 | `#@native.type = #{type}`
74 | end
75 |
76 | def append(child)
77 | `#@native.appendChild(#{child.native})`
78 | end
79 |
80 | alias << append
81 |
82 | def css_text=(text)
83 | %x{
84 | if (#@native.styleSheet) {
85 | #@native.styleSheet.cssText = #{text};
86 | }
87 | else {
88 | #@native.appendChild(document.createTextNode(#{text}));
89 | }
90 | }
91 | end
92 |
93 | def style(name, value)
94 | `#@native.style[#{name}] = value`
95 | end
96 |
97 | def append_to_head
98 | `document.getElementsByTagName('head')[0].appendChild(#@native)`
99 | end
100 | end
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/lib-opal/opal/rspec/formatter/html_printer.rb:
--------------------------------------------------------------------------------
1 | # backtick_javascript: true
2 |
3 | require_relative 'noop_flush_string_io'
4 | require_relative 'element'
5 |
6 | module Opal
7 | module RSpec
8 | class HtmlPrinter < ::RSpec::Core::Formatters::HtmlPrinter
9 | def initialize(output)
10 | super
11 | @group_stack = []
12 | @update_stack = []
13 | end
14 |
15 | def print_html_start
16 | # Will output the header
17 | super
18 | # Now close out the doc so we can use DOM manipulation for the rest
19 | @output.puts ""
20 | @output.puts ""
21 | @output.puts "