From dc88147147c4637b3a189473a4b4136cc72c9a6a Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Fri, 7 Jun 2019 16:31:46 -0400 Subject: [PATCH 001/307] It works, but specs need to be updated --- ruby/hyper-operation/hyper-operation.gemspec | 1 + .../hyper-operation/transport/connection.rb | 184 +++++------------- .../connection_adapter/active_record.rb | 111 +++++++++++ .../active_record/auto_create.rb | 26 +++ .../active_record/connection.rb | 47 +++++ .../active_record/queued_message.rb | 40 ++++ .../transport/connection_adapter/redis.rb | 94 +++++++++ .../connection_adapter/redis/connection.rb | 84 ++++++++ .../redis/queued_message.rb | 43 ++++ .../connection_adapter/redis/redis_record.rb | 111 +++++++++++ .../hyper-operation/transport/hyperstack.rb | 10 +- 11 files changed, 614 insertions(+), 137 deletions(-) create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb create mode 100644 ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index 49b95945f..d96b4c48f 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -44,6 +44,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rails', '>= 4.0.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' + spec.add_development_dependency 'redis' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rspec-wait' diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb index 565e83633..ccb169d0c 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb @@ -1,173 +1,85 @@ -module Hyperstack - module AutoCreate - def table_exists? - # works with both rails 4 and 5 without deprecation warnings - if connection.respond_to?(:data_sources) - connection.data_sources.include?(table_name) - else - connection.tables.include?(table_name) - end - end - - def needs_init? - Hyperstack.transport != :none && Hyperstack.on_server? && !table_exists? - end - - def create_table(*args, &block) - connection.create_table(table_name, *args, &block) if needs_init? - end - end - - class Connection < ActiveRecord::Base - class QueuedMessage < ActiveRecord::Base - - extend AutoCreate - - self.table_name = 'hyperstack_queued_messages' +# frozen_string_literal: true - do_not_synchronize - - serialize :data - - belongs_to :hyperstack_connection, - class_name: 'Hyperstack::Connection', - foreign_key: 'connection_id' - - scope :for_session, - ->(session) { joins(:hyperstack_connection).where('session = ?', session) } - - # For simplicity we use QueuedMessage with connection_id 0 - # to store the current path which is used by consoles to - # communicate back to the server - - default_scope { where('connection_id IS NULL OR connection_id != 0') } - - def self.root_path=(path) - unscoped.find_or_create_by(connection_id: 0).update(data: path) - end +module Hyperstack + class Connection + class << self + attr_accessor :transport, :connection_adapter + + def adapter + adapter_name = Hyperstack.connection[:adapter].to_s + adapter_path = "hyper-operation/transport/connection_adapter/#{adapter_name}" + + begin + require adapter_path + rescue LoadError => e + if e.path == adapter_path + raise e.class, "Could not load the '#{adapter_name}' adapter. Make sure the adapter is spelled correctly in your Hyperstack config, and the necessary gems are in your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{adapter_name}' adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end + end - def self.root_path - unscoped.find_or_create_by(connection_id: 0).data + adapter_name = adapter_name.camelize + "Hyperstack::ConnectionAdapter::#{adapter_name}".constantize end - end - extend AutoCreate - - def self.build_tables - create_table(force: :cascade) do |t| - t.string :channel - t.string :session - t.datetime :created_at - t.datetime :expires_at - t.datetime :refresh_at - end - QueuedMessage.create_table(force: :cascade) do |t| - t.text :data - t.integer :connection_id + def build_tables + adapter.build_tables end - end - - do_not_synchronize - - self.table_name = 'hyperstack_connections' - - has_many :messages, - foreign_key: 'connection_id', - class_name: 'Hyperstack::Connection::QueuedMessage', - dependent: :destroy - scope :expired, - -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.zone.now) } - scope :pending_for, - ->(channel) { where(channel: channel).where('session IS NOT NULL') } - scope :inactive, - -> { where('session IS NULL AND refresh_at < ?', Time.zone.now) } - def self.needs_refresh? - exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.zone.now]) - end - - def transport - self.class.transport - end - - before_create do - if session - self.expires_at = Time.now + transport.expire_new_connection_in - elsif transport.refresh_channels_every != :never - self.refresh_at = Time.now + transport.refresh_channels_every + def build_tables? + adapter.respond_to?(:build_tables) end - end - - class << self - attr_accessor :transport def active - # if table doesn't exist then we are either calling from within - # a migration or from a console before the server has ever started - # in these cases there are no channels so we return nothing - return [] unless table_exists? - if Hyperstack.on_server? - expired.delete_all - refresh_connections if needs_refresh? - end - all.pluck(:channel).uniq + adapter.active end def open(channel, session = nil, root_path = nil) - self.root_path = root_path - find_or_create_by(channel: channel, session: session) + adapter.open(channel, session, root_path) end def send_to_channel(channel, data) - pending_for(channel).each do |connection| - QueuedMessage.create(data: data, hyperstack_connection: connection) - end - transport.send_data(channel, data) if exists?(channel: channel, session: nil) + adapter.send_to_channel(channel, data) end def read(session, root_path) - self.root_path = root_path - where(session: session) - .update_all(expires_at: Time.now + transport.expire_polled_connection_in) - QueuedMessage.for_session(session).destroy_all.pluck(:data) + adapter.read(session, root_path) end def connect_to_transport(channel, session, root_path) - self.root_path = root_path - if (connection = find_by(channel: channel, session: session)) - messages = connection.messages.pluck(:data) - connection.destroy - else - messages = [] - end - open(channel) - messages + adapter.connect_to_transport(channel, session, root_path) end def disconnect(channel) - find_by(channel: channel, session: nil).destroy + adapter.disconnect(channel) end def root_path=(path) - QueuedMessage.root_path = path if path + adapter.root_path = path end def root_path - # if the QueuedMessage table doesn't exist then we are either calling from within - # a migration or from a console before the server has ever started - # in these cases there is no root path to the server - QueuedMessage.root_path if QueuedMessage.table_exists? + adapter.root_path end def refresh_connections - refresh_started_at = Time.zone.now - channels = transport.refresh_channels - next_refresh = refresh_started_at + transport.refresh_channels_every - channels.each do |channel| - connection = find_by(channel: channel, session: nil) - connection.update(refresh_at: next_refresh) if connection + adapter.refresh_connections + end + + def method_missing(method_name, *args, &block) + if adapter::Connection.respond_to?(method_name) + adapter::Connection.send(method_name, *args, &block) + else + super end - inactive.delete_all + end + + def respond_to_missing?(method_name, include_private = false) + adapter::Connection.respond_to?(method_name) end end end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb new file mode 100644 index 000000000..e1a071115 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require_relative 'active_record/connection' +require_relative 'active_record/queued_message' + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + class << self + def build_tables + Connection.create_table(force: :cascade) do |t| + t.string :channel + t.string :session + t.datetime :created_at + t.datetime :expires_at + t.datetime :refresh_at + end + + QueuedMessage.create_table(force: :cascade) do |t| + t.text :data + t.integer :connection_id + end + end + + def transport + Hyperstack::Connection.transport + end + + def active + # if table doesn't exist then we are either calling from within + # a migration or from a console before the server has ever started + # in these cases there are no channels so we return nothing + return [] unless Connection.table_exists? + + if Hyperstack.on_server? + Connection.expired.delete_all + refresh_connections if Connection.needs_refresh? + end + + Connection.all.pluck(:channel).uniq + end + + def open(channel, session = nil, root_path = nil) + self.root_path = root_path + + Connection.find_or_create_by(channel: channel, session: session) + end + + def send_to_channel(channel, data) + Connection.pending_for(channel).each do |connection| + QueuedMessage.create(data: data, hyperstack_connection: connection) + end + + transport.send_data(channel, data) if Connection.exists?(channel: channel, session: nil) + end + + def read(session, root_path) + self.root_path = root_path + + Connection.where(session: session) + .update_all(expires_at: Time.now + transport.expire_polled_connection_in) + + QueuedMessage.for_session(session).destroy_all.pluck(:data) + end + + def connect_to_transport(channel, session, root_path) + self.root_path = root_path + + if (connection = Connection.find_by(channel: channel, session: session)) + messages = connection.messages.pluck(:data) + connection.destroy + else + messages = [] + end + + open(channel) + + messages + end + + def disconnect(channel) + Connection.find_by(channel: channel, session: nil).destroy + end + + def root_path=(path) + QueuedMessage.root_path = path if path + end + + def root_path + # if the QueuedMessage table doesn't exist then we are either calling from within + # a migration or from a console before the server has ever started + # in these cases there is no root path to the server + QueuedMessage.root_path if QueuedMessage.table_exists? + end + + def refresh_connections + refresh_started_at = Time.zone.now + channels = transport.refresh_channels + next_refresh = refresh_started_at + transport.refresh_channels_every + + channels.each do |channel| + connection = Connection.find_by(channel: channel, session: nil) + connection.update(refresh_at: next_refresh) if connection + end + + Connection.inactive.delete_all + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb new file mode 100644 index 000000000..604ef62f3 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/auto_create.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + module AutoCreate + def table_exists? + # works with both rails 4 and 5 without deprecation warnings + if connection.respond_to?(:data_sources) + connection.data_sources.include?(table_name) + else + connection.tables.include?(table_name) + end + end + + def needs_init? + Hyperstack.transport != :none && Hyperstack.on_server? && !table_exists? + end + + def create_table(*args, &block) + connection.create_table(table_name, *args, &block) if needs_init? + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb new file mode 100644 index 000000000..0d9044070 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'auto_create' + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + class Connection < ::ActiveRecord::Base + extend AutoCreate + + self.table_name = 'hyperstack_connections' + + do_not_synchronize + + has_many :messages, + foreign_key: 'connection_id', + class_name: 'Hyperstack::ConnectionAdapter::ActiveRecord::QueuedMessage', + dependent: :destroy + + scope :expired, + -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.zone.now) } + scope :pending_for, + ->(channel) { where(channel: channel).where('session IS NOT NULL') } + scope :inactive, + -> { where('session IS NULL AND refresh_at < ?', Time.zone.now) } + + before_create do + if session + self.expires_at = Time.now + transport.expire_new_connection_in + elsif transport.refresh_channels_every != :never + self.refresh_at = Time.now + transport.refresh_channels_every + end + end + + class << self + def needs_refresh? + exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.zone.now]) + end + end + + def transport + Hyperstack::Connection.transport + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb new file mode 100644 index 000000000..8781f90d6 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative 'auto_create' + +module Hyperstack + module ConnectionAdapter + module ActiveRecord + class QueuedMessage < ::ActiveRecord::Base + extend AutoCreate + + self.table_name = 'hyperstack_queued_messages' + + do_not_synchronize + + serialize :data + + belongs_to :hyperstack_connection, + class_name: 'Hyperstack::ConnectionAdapter::ActiveRecord::Connection', + foreign_key: 'connection_id' + + scope :for_session, + ->(session) { joins(:hyperstack_connection).where('session = ?', session) } + + # For simplicity we use QueuedMessage with connection_id 0 + # to store the current path which is used by consoles to + # communicate back to the server + + default_scope { where('connection_id IS NULL OR connection_id != 0') } + + def self.root_path=(path) + unscoped.find_or_create_by(connection_id: 0).update(data: path) + end + + def self.root_path + unscoped.find_or_create_by(connection_id: 0).data + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb new file mode 100644 index 000000000..30c0cee66 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require_relative 'redis/connection' +require_relative 'redis/queued_message' + +module Hyperstack + module ConnectionAdapter + module Redis + class << self + def transport + Hyperstack::Connection.transport + end + + def active + if Hyperstack.on_server? + Connection.expired.each(&:destroy) + refresh_connections if Connection.needs_refresh? + end + + Connection.all.map(&:channel).uniq + end + + def open(channel, session = nil, root_path = nil) + self.root_path = root_path + + Connection.find_or_create_by(channel: channel, session: session) + end + + def send_to_channel(channel, data) + Connection.pending_for(channel).each do |connection| + QueuedMessage.create(connection_id: connection.id, data: data) + end + + transport.send_data(channel, data) if Connection.exists?(channel: channel, session: '') + end + + def read(session, root_path) + self.root_path = root_path + + Connection.where(session: session).each do |connection| + connection.update(expires_at: Time.now + transport.expire_polled_connection_in) + end + + messages = QueuedMessage.for_session(session) + data = messages.map(&:data) + messages.each(&:destroy) + data + end + + def connect_to_transport(channel, session, root_path) + self.root_path = root_path + + if (connection = Connection.find_by(channel: channel, session: session)) + messages = connection.messages.map(&:data) + connection.destroy + else + messages = [] + end + + open(channel) + + messages + end + + def disconnect(channel) + Connection.find_by(channel: channel, session: '').each(&:destroy) + end + + def root_path=(path) + QueuedMessage.root_path = path if path + end + + def root_path + QueuedMessage.root_path + rescue + nil + end + + def refresh_connections + refresh_started_at = Time.zone.now + channels = transport.refresh_channels + next_refresh = refresh_started_at + transport.refresh_channels_every + + channels.each do |channel| + connection = Connection.find_by(channel: channel, session: '') + connection.update(refresh_at: next_refresh) if connection + end + + Connection.inactive.each(&:destroy) + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb new file mode 100644 index 000000000..c4ffdafe5 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require_relative 'redis_record' + +module Hyperstack + module ConnectionAdapter + module Redis + class Connection < RedisRecord::Base + self.table_name = 'hyperstack:connections' + self.column_names = %w[id channel session created_at expires_at refresh_at].freeze + + attr_accessor(*column_names.map(&:to_sym)) + + def messages + QueuedMessage.where(connection_id: id) + end + + class << self + def transport + Hyperstack::Connection.transport + end + + def create(opts = {}) + id = SecureRandom.uuid + + opts.tap do |hash| + hash[:id] = id + + if opts[:session] + hash[:expires_at] = Time.now + transport.expire_new_connection_in + else + hash[:refresh_at] = Time.now + transport.refresh_channels_every + end + + hash[:created_at] = Time.now + end.to_a.flatten + + client.hmset("#{table_name}:#{id}", *opts) + client.sadd(table_name, id) + + new(client.hgetall("#{table_name}:#{id}")) + end + + def inactive + scope { |id| new(client.hgetall("#{table_name}:#{id}")) if inactive?(id) } + end + + def inactive?(id) + client.hget("#{table_name}:#{id}", :session).blank? && + client.hexists("h#{table_name}:#{id}", :refresh_at) && + Time.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.zone.now + end + + def expired + scope { |id| new(client.hgetall("#{table_name}:#{id}")) if expired?(id) } + end + + def expired?(id) + client.hexists("#{table_name}:#{id}", :expires_at) && + Time.parse(client.hget("#{table_name}:#{id}", :expires_at)) < Time.zone.now + end + + def pending_for(channel) + scope { |id| new(client.hgetall("#{table_name}:#{id}")) if pending_for?(id, channel) } + end + + def pending_for?(id, channel) + !client.hget("#{table_name}:#{id}", :session).blank? && + client.hget("#{table_name}:#{id}", :channel) == channel + end + + def needs_refresh? + scope { |id| new(client.hgetall("#{table_name}:#{id}")) if needs_refresh(id) } + end + + def needs_refresh(id) + client.hexists("#{table_name}:#{id}", :refresh_at) && + Time.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.zone.now + end + end + end + end + end +end \ No newline at end of file diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb new file mode 100644 index 000000000..02da60a17 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative 'redis_record' + +module Hyperstack + module ConnectionAdapter + module Redis + class QueuedMessage < RedisRecord::Base + self.table_name = 'hyperstack:queued_messages' + self.column_names = %w[id data connection_id].freeze + + attr_accessor(*column_names.map(&:to_sym)) + + class << self + def create(opts = {}) + id = SecureRandom.uuid + + client.hmset("#{table_name}:#{id}", *opts.merge(id: id)) + client.sadd(table_name, id) + + new(client.hgetall("#{table_name}:#{id}")) + end + + def for_session(session) + Connection.where(session: session).map(&:messages).flatten + end + + def root_path=(path) + find_or_create_by(connection_id: 0).update(data: path) + end + + def root_path + find_or_create_by(connection_id: 0).data + end + end + + def connection + Connection.find(connection_id) + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb new file mode 100644 index 000000000..6fe0c84f6 --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module Hyperstack + module ConnectionAdapter + module Redis + module RedisRecord + class Base + class << self + attr_accessor :table_name, :column_names + + def client + @client ||= ::Redis.new(url: Hyperstack.connection[:redis_url]) + end + + def scope(&block) + client.smembers(table_name).map(&block).compact + end + + def all + scope { |id| new(client.hgetall("#{table_name}:#{id}")) } + end + + def first + id = client.smembers(table_name).first + + new(client.hgetall("#{table_name}:#{id}")) + end + + def last + id = client.smembers(table_name).last + + new(client.hgetall("#{table_name}:#{id}")) + end + + def find(id) + return unless client.smembers(table_name).include?(id) + + client.hget("#{table_name}:#{id}") + end + + def find_by(opts) + found = nil + + client.smembers(table_name).each do |id| + unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v }.include?(false) + found = new(client.hgetall("#{table_name}:#{id}")) + break + end + end + + found + end + + def find_or_create_by(opts = {}) + if (existing = find_by(opts)) + existing + else + create(opts) + end + end + + def where(opts = {}) + scope do |id| + unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v }.include?(false) + new(client.hgetall("#{table_name}:#{id}")) + end + end + end + + def exists?(opts = {}) + !!client.smembers(table_name).detect do |uuid| + !opts.map { |k, v| client.hget("#{table_name}:#{uuid}", k) == v }.include?(false) + end + end + + def destroy_all + all.each(&:destroy) + end + end + + def initialize(opts = {}) + opts.each { |k, v| send(:"#{k}=", v) } + end + + def save + self.class.client.hmset("#{table_name}:#{id}", *attributes) + end + + def update(opts = {}) + opts.each { |k, v| send(:"#{k}=", v) } + save + end + + def destroy + self.class.client.srem(table_name, id) + + self.class.client.hdel("#{table_name}:#{id}", attributes.keys) + end + + def attributes + self.class.client.hgetall("#{table_name}:#{id}") + end + + def table_name + self.class.table_name + end + end + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb index 1ba9df7b7..1d78cc26b 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack.rb @@ -16,7 +16,7 @@ def self.reset_operations # config.eager_load_paths += %W(#{config.root}/app/hyperstack/models) # config.autoload_paths += %W(#{config.root}/app/hyperstack/models) # config.assets.paths << ::Rails.root.join('app', 'hyperstack').to_s - config.after_initialize { Connection.build_tables } + config.after_initialize { Connection.build_tables if Connection.build_tables? } end Object.send(:remove_const, :Application) if @fake_application_defined @fake_application_defined = false @@ -66,6 +66,14 @@ def self.reset_operations define_setting :client_logging, true define_setting :connect_session, true + define_setting(:connection, { adapter: :active_record }) do |connection| + if connection[:adapter] == :redis + require 'redis' + + connection[:redis_url] ||= 'redis://127.0.0.1:6379/hyperstack' + end + end + def self.app_id opts[:app_id] || Pusher.app_id if transport == :pusher end From 22061ae91b24a7ad4e0e96494fbbf8c833e6bf15 Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Wed, 4 Dec 2019 14:24:08 -0500 Subject: [PATCH 002/307] update connection spec, fix Time consistency/Redis transport --- .../connection_adapter/active_record.rb | 4 +- .../active_record/connection.rb | 10 +- .../transport/connection_adapter/redis.rb | 4 +- .../connection_adapter/redis/connection.rb | 33 +-- .../redis/queued_message.rb | 9 - .../connection_adapter/redis/redis_record.rb | 30 +- .../spec/aaa_run_first/connection_spec.rb | 256 ++++++++++-------- 7 files changed, 184 insertions(+), 162 deletions(-) diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb index e1a071115..fa7938199 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb @@ -58,7 +58,7 @@ def read(session, root_path) self.root_path = root_path Connection.where(session: session) - .update_all(expires_at: Time.now + transport.expire_polled_connection_in) + .update_all(expires_at: Time.current + transport.expire_polled_connection_in) QueuedMessage.for_session(session).destroy_all.pluck(:data) end @@ -94,7 +94,7 @@ def root_path end def refresh_connections - refresh_started_at = Time.zone.now + refresh_started_at = Time.current channels = transport.refresh_channels next_refresh = refresh_started_at + transport.refresh_channels_every diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb index 0d9044070..7c6fbd4e9 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/connection.rb @@ -18,23 +18,23 @@ class Connection < ::ActiveRecord::Base dependent: :destroy scope :expired, - -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.zone.now) } + -> { where('expires_at IS NOT NULL AND expires_at < ?', Time.current) } scope :pending_for, ->(channel) { where(channel: channel).where('session IS NOT NULL') } scope :inactive, - -> { where('session IS NULL AND refresh_at < ?', Time.zone.now) } + -> { where('session IS NULL AND refresh_at < ?', Time.current) } before_create do if session - self.expires_at = Time.now + transport.expire_new_connection_in + self.expires_at = Time.current + transport.expire_new_connection_in elsif transport.refresh_channels_every != :never - self.refresh_at = Time.now + transport.refresh_channels_every + self.refresh_at = Time.current + transport.refresh_channels_every end end class << self def needs_refresh? - exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.zone.now]) + exists?(['refresh_at IS NOT NULL AND refresh_at < ?', Time.current]) end end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb index 30c0cee66..7fb330ab5 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb @@ -38,7 +38,7 @@ def read(session, root_path) self.root_path = root_path Connection.where(session: session).each do |connection| - connection.update(expires_at: Time.now + transport.expire_polled_connection_in) + connection.update(expires_at: Time.current + transport.expire_polled_connection_in) end messages = QueuedMessage.for_session(session) @@ -77,7 +77,7 @@ def root_path end def refresh_connections - refresh_started_at = Time.zone.now + refresh_started_at = Time.current channels = transport.refresh_channels next_refresh = refresh_started_at + transport.refresh_channels_every diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb index c4ffdafe5..71369e767 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb @@ -21,24 +21,17 @@ def transport end def create(opts = {}) - id = SecureRandom.uuid - opts.tap do |hash| - hash[:id] = id - if opts[:session] - hash[:expires_at] = Time.now + transport.expire_new_connection_in + hash[:expires_at] = (Time.current + transport.expire_new_connection_in) else - hash[:refresh_at] = Time.now + transport.refresh_channels_every + hash[:refresh_at] = (Time.current + transport.refresh_channels_every) end - hash[:created_at] = Time.now + hash[:created_at] = Time.current end.to_a.flatten - client.hmset("#{table_name}:#{id}", *opts) - client.sadd(table_name, id) - - new(client.hgetall("#{table_name}:#{id}")) + super(opts) end def inactive @@ -47,8 +40,8 @@ def inactive def inactive?(id) client.hget("#{table_name}:#{id}", :session).blank? && - client.hexists("h#{table_name}:#{id}", :refresh_at) && - Time.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.zone.now + client.hget("#{table_name}:#{id}", :refresh_at).present? && + Time.zone.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.current end def expired @@ -56,8 +49,8 @@ def expired end def expired?(id) - client.hexists("#{table_name}:#{id}", :expires_at) && - Time.parse(client.hget("#{table_name}:#{id}", :expires_at)) < Time.zone.now + client.hget("#{table_name}:#{id}", :expires_at).present? && + Time.zone.parse(client.hget("#{table_name}:#{id}", :expires_at)) < Time.current end def pending_for(channel) @@ -65,20 +58,20 @@ def pending_for(channel) end def pending_for?(id, channel) - !client.hget("#{table_name}:#{id}", :session).blank? && + client.hget("#{table_name}:#{id}", :session).present? && client.hget("#{table_name}:#{id}", :channel) == channel end def needs_refresh? - scope { |id| new(client.hgetall("#{table_name}:#{id}")) if needs_refresh(id) } + scope { |id| new(client.hgetall("#{table_name}:#{id}")) if needs_refresh(id) }.any? end def needs_refresh(id) - client.hexists("#{table_name}:#{id}", :refresh_at) && - Time.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.zone.now + client.hget("#{table_name}:#{id}", :refresh_at).present? && + Time.zone.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.current end end end end end -end \ No newline at end of file +end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb index 02da60a17..c23c28f7e 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/queued_message.rb @@ -12,15 +12,6 @@ class QueuedMessage < RedisRecord::Base attr_accessor(*column_names.map(&:to_sym)) class << self - def create(opts = {}) - id = SecureRandom.uuid - - client.hmset("#{table_name}:#{id}", *opts.merge(id: id)) - client.sadd(table_name, id) - - new(client.hgetall("#{table_name}:#{id}")) - end - def for_session(session) Connection.where(session: session).map(&:messages).flatten end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb index 6fe0c84f6..411e7039f 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb @@ -35,14 +35,14 @@ def last def find(id) return unless client.smembers(table_name).include?(id) - client.hget("#{table_name}:#{id}") + new(client.hgetall("#{table_name}:#{id}")) end def find_by(opts) found = nil client.smembers(table_name).each do |id| - unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v }.include?(false) + unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v.to_s }.include?(false) found = new(client.hgetall("#{table_name}:#{id}")) break end @@ -61,7 +61,7 @@ def find_or_create_by(opts = {}) def where(opts = {}) scope do |id| - unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v }.include?(false) + unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v.to_s }.include?(false) new(client.hgetall("#{table_name}:#{id}")) end end @@ -69,12 +69,22 @@ def where(opts = {}) def exists?(opts = {}) !!client.smembers(table_name).detect do |uuid| - !opts.map { |k, v| client.hget("#{table_name}:#{uuid}", k) == v }.include?(false) + !opts.map { |k, v| client.hget("#{table_name}:#{uuid}", k) == v.to_s }.include?(false) end end + def create(opts = {}) + record = new({ id: SecureRandom.uuid }.merge(opts)) + + record.save + + record + end + def destroy_all all.each(&:destroy) + + true end end @@ -84,6 +94,12 @@ def initialize(opts = {}) def save self.class.client.hmset("#{table_name}:#{id}", *attributes) + + unless self.class.client.smembers(table_name).include?(id) + self.class.client.sadd(table_name, id) + end + + true end def update(opts = {}) @@ -95,10 +111,14 @@ def destroy self.class.client.srem(table_name, id) self.class.client.hdel("#{table_name}:#{id}", attributes.keys) + + true end def attributes - self.class.client.hgetall("#{table_name}:#{id}") + self.class.column_names.map do |column_name| + [column_name, instance_variable_get("@#{column_name}")] + end.to_h end def table_name diff --git a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb index f4e27d908..71abec46e 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb @@ -1,152 +1,170 @@ require 'spec_helper' -describe Hyperstack::Connection do +%w[active_record redis].each do |adapter| + require adapter + + describe Hyperstack::Connection do + if adapter == 'active_record' + if ActiveRecord::Base.connection.respond_to? :data_sources + it 'creates the tables (rails 5.x)' do + ActiveRecord::Base.connection.data_sources.should include('hyperstack_connections') + ActiveRecord::Base.connection.data_sources.should include('hyperstack_queued_messages') + + expect(described_class.column_names) + .to match(%w[id channel session created_at expires_at refresh_at]) + end + else + it 'creates the tables (rails 4.x)' do + ActiveRecord::Base.connection.tables.should include('hyperstack_connections') + ActiveRecord::Base.connection.tables.should include('hyperstack_queued_messages') + + expect(described_class.column_names) + .to match(%w['id channel session created_at expires_at refresh_at]) + end + end + end - before(:all) do - Hyperstack.configuration do |config| + before(:all) do + Hyperstack.configuration do |config| + config.connection = { adapter: adapter } + end end - end - before(:each) do - Timecop.return - end + before(:each) do + Timecop.return - if ActiveRecord::Base.connection.respond_to? :data_sources - it 'creates the tables (rails 5.x)' do - ActiveRecord::Base.connection.data_sources.should include('hyperstack_connections') - ActiveRecord::Base.connection.data_sources.should include('hyperstack_queued_messages') - described_class.column_names.should =~ ['id', 'channel', 'session', 'created_at', 'expires_at', 'refresh_at'] - end - else - it 'creates the tables (rails 4.x)' do - ActiveRecord::Base.connection.tables.should include('hyperstack_connections') - ActiveRecord::Base.connection.tables.should include('hyperstack_queued_messages') - described_class.column_names.should =~ ['id', 'channel', 'session', 'created_at', 'expires_at', 'refresh_at'] + described_class.adapter::Connection.destroy_all + described_class.adapter::QueuedMessage.destroy_all end - end - it 'creates the messages queue' do - channel = described_class.new - channel.messages << Hyperstack::Connection::QueuedMessage.new - channel.save - channel.reload - channel.messages.should eq(Hyperstack::Connection::QueuedMessage.all) - end + it 'creates the messages queue' do + channel = described_class.adapter::Connection.create + channel.messages << described_class.adapter::QueuedMessage.new - it 'can set the root path' do - described_class.root_path = 'foobar' - expect(described_class.root_path).to eq('foobar') - end - - it 'adding new connection' do - described_class.open('TestChannel', 0) - expect(described_class.active).to eq(['TestChannel']) - end + channel.messages.should eq(described_class.adapter::QueuedMessage.all) + end - it 'new connections expire' do - described_class.open('TestChannel', 0) - expect(described_class.active).to eq(['TestChannel']) - Timecop.travel(Time.now+described_class.transport.expire_new_connection_in) - expect(described_class.active).to eq([]) - end + it 'can set the root path' do + described_class.root_path = 'foobar' + expect(described_class.root_path).to eq('foobar') + end - it 'can send and read data from a channel' do - described_class.open('TestChannel', 0) - described_class.open('TestChannel', 1) - described_class.open('AnotherChannel', 0) - described_class.send_to_channel('TestChannel', 'data') - expect(described_class.read(0, 'path')).to eq(['data']) - expect(described_class.read(0, 'path')).to eq([]) - expect(described_class.read(1, 'path')).to eq(['data']) - expect(described_class.read(1, 'path')).to eq([]) - expect(described_class.read(0, 'path')).to eq([]) - end + it 'adding new connection' do + described_class.open('TestChannel', 0) - it 'will update the expiration time after reading' do - described_class.open('TestChannel', 0) - described_class.send_to_channel('TestChannel', 'data') - described_class.read(0, 'path') - Timecop.travel(Time.now+described_class.transport.expire_new_connection_in) - expect(described_class.active).to eq(['TestChannel']) - end + expect(described_class.active).to eq(['TestChannel']) + end - it 'will expire a polled connection' do - described_class.open('TestChannel', 0) - described_class.send_to_channel('TestChannel', 'data') - described_class.read(0, 'path') - Timecop.travel(Time.now+described_class.transport.expire_polled_connection_in) - expect(described_class.active).to eq([]) - end + it 'new connections expire' do + described_class.open('TestChannel', 0) + expect(described_class.active).to eq(['TestChannel']) + Timecop.travel(Time.now + described_class.transport.expire_new_connection_in) + expect(described_class.active).to eq([]) + end - context 'after connecting to the transport' do - before(:each) do + it 'can send and read data from a channel' do described_class.open('TestChannel', 0) described_class.open('TestChannel', 1) + described_class.open('AnotherChannel', 0) described_class.send_to_channel('TestChannel', 'data') + expect(described_class.read(0, 'path')).to eq(['data']) + expect(described_class.read(0, 'path')).to eq([]) + expect(described_class.read(1, 'path')).to eq(['data']) + expect(described_class.read(1, 'path')).to eq([]) + expect(described_class.read(0, 'path')).to eq([]) end - it "will pass any pending data back" do - expect(described_class.connect_to_transport('TestChannel', 0, nil)).to eq(['data']) - end - - it "will have the root path set for console access" do - described_class.connect_to_transport('TestChannel', 0, "some_path") - expect(Hyperstack::Connection.root_path).to eq("some_path") - end + it 'will update the expiration time after reading' do + described_class.open('TestChannel', 0) + described_class.send_to_channel('TestChannel', 'data') + described_class.read(0, 'path') + Timecop.travel(Time.now + described_class.transport.expire_new_connection_in) - it "the channel will still be active even after initial connection time is expired" do - described_class.connect_to_transport('TestChannel', 0, nil) - Timecop.travel(Time.now+described_class.transport.expire_new_connection_in) expect(described_class.active).to eq(['TestChannel']) end - it "will only effect the session being connected" do - described_class.connect_to_transport('TestChannel', 0, nil) - expect(described_class.read(1, 'path')).to eq(['data']) - end + it 'will expire a polled connection' do + described_class.open('TestChannel', 0) + described_class.send_to_channel('TestChannel', 'data') + described_class.read(0, 'path') + Timecop.travel(Time.now + described_class.transport.expire_polled_connection_in) - it "will begin refreshing the channel list" do - allow(Hyperstack).to receive(:refresh_channels) {['AnotherChannel']} - described_class.open('AnotherChannel', 0) - described_class.connect_to_transport('TestChannel', 0, nil) - described_class.connect_to_transport('AnotherChannel', 0, nil) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - Timecop.travel(Time.now+described_class.transport.refresh_channels_every) - expect(described_class.active).to eq(['AnotherChannel']) + expect(described_class.active).to eq([]) end - it "refreshing will not effect channels not connected to the transport" do - allow(Hyperstack).to receive(:refresh_channels) {['AnotherChannel']} - described_class.open('AnotherChannel', 0) - described_class.connect_to_transport('TestChannel', 0, nil) - Timecop.travel(Time.now+described_class.transport.refresh_channels_every-1) - described_class.read(1, 'path') - described_class.connect_to_transport('AnotherChannel', 0, nil) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - Timecop.travel(Time.now+1) - described_class.active - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - end + context 'after connecting to the transport' do + before(:each) do + described_class.open('TestChannel', 0) + described_class.open('TestChannel', 1) + described_class.send_to_channel('TestChannel', 'data') + end + + it 'will pass any pending data back' do + expect(described_class.connect_to_transport('TestChannel', 0, nil)).to eq(['data']) + end + + it 'will have the root path set for console access' do + described_class.connect_to_transport('TestChannel', 0, 'some_path') + expect(Hyperstack::Connection.root_path).to eq('some_path') + end - it "refreshing will not effect channels added during the refresh" do - allow(Hyperstack).to receive(:refresh_channels) do + it 'the channel will still be active even after initial connection time is expired' do described_class.connect_to_transport('TestChannel', 0, nil) - ['AnotherChannel'] + Timecop.travel(Time.now + described_class.transport.expire_new_connection_in) + expect(described_class.active.sort).to eq(['TestChannel'].sort) end - described_class.open('AnotherChannel', 0) - Timecop.travel(Time.now+described_class.transport.refresh_channels_every) - described_class.read(0, 'path') - described_class.connect_to_transport('AnotherChannel', 0, nil) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - described_class.open('TestChannel', 2) - expect(described_class.active).to eq(['TestChannel', 'AnotherChannel']) - end - it "sends messages to the transport as well as open channels" do - expect(Hyperstack).to receive(:send_data).with('TestChannel', 'data2') - described_class.connect_to_transport('TestChannel', 0, nil) - described_class.send_to_channel('TestChannel', 'data2') - expect(described_class.read(1, 'path')).to eq(['data', 'data2']) + it 'will only effect the session being connected' do + described_class.connect_to_transport('TestChannel', 0, nil) + expect(described_class.read(1, 'path')).to eq(['data']) + end + + it 'will begin refreshing the channel list' do + allow(Hyperstack).to receive(:refresh_channels) { ['AnotherChannel'] } + described_class.open('AnotherChannel', 0) + described_class.connect_to_transport('TestChannel', 0, nil) + described_class.connect_to_transport('AnotherChannel', 0, nil) + + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + + Timecop.travel(Time.now + described_class.transport.refresh_channels_every) + + expect(described_class.active.sort).to eq(['AnotherChannel'].sort) + end + + it 'refreshing will not effect channels not connected to the transport' do + allow(Hyperstack).to receive(:refresh_channels) { ['AnotherChannel'] } + described_class.open('AnotherChannel', 0) + described_class.connect_to_transport('TestChannel', 0, nil) + Timecop.travel(Time.now + described_class.transport.refresh_channels_every - 1) + described_class.read(1, 'path') + described_class.connect_to_transport('AnotherChannel', 0, nil) + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + Timecop.travel(Time.now + 1) + described_class.active + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + end + + it 'refreshing will not effect channels added during the refresh' do + allow(Hyperstack).to receive(:refresh_channels) do + described_class.connect_to_transport('TestChannel', 0, nil) + ['AnotherChannel'] + end + described_class.open('AnotherChannel', 0) + Timecop.travel(Time.now + described_class.transport.refresh_channels_every) + described_class.read(0, 'path') + described_class.connect_to_transport('AnotherChannel', 0, nil) + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + described_class.open('TestChannel', 2) + expect(described_class.active.sort).to eq(%w[TestChannel AnotherChannel].sort) + end + + it 'sends messages to the transport as well as open channels' do + expect(Hyperstack).to receive(:send_data).with('TestChannel', 'data2') + described_class.connect_to_transport('TestChannel', 0, nil) + described_class.send_to_channel('TestChannel', 'data2') + expect(described_class.read(1, 'path').sort).to eq(%w[data data2].sort) + end end end end From 8f995ea3f71d2b71445583cfcf3f4f0a21f7bc5a Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Wed, 4 Dec 2019 15:04:13 -0500 Subject: [PATCH 003/307] add redis to hyper-operation travis.yml --- ruby/hyper-operation/.travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby/hyper-operation/.travis.yml b/ruby/hyper-operation/.travis.yml index 3eebae118..16e36bca3 100644 --- a/ruby/hyper-operation/.travis.yml +++ b/ruby/hyper-operation/.travis.yml @@ -7,6 +7,7 @@ rvm: - ruby-head services: - mysql + - redis-server env: - DRIVER=google-chrome TZ=Europe/Berlin matrix: From 8b441119fc0f9a8495c57e3d20eaebfe9b25cd2e Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Wed, 4 Dec 2019 15:24:34 -0500 Subject: [PATCH 004/307] add redis-server to master travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 081e5ed1e..4c1df777a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ _test_gem: &_test_gem - chromium-chromedriver - google-chrome-stable - yarn + - redis-server mariadb: '10.3' before_install: - echo installing $COMPONENT From d7fb8d8a0b2846482b1601ecb5bc86cbaac2e5a5 Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Thu, 5 Dec 2019 11:31:51 -0500 Subject: [PATCH 005/307] Fix specs --- ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb index 71abec46e..06501de08 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -%w[active_record redis].each do |adapter| - require adapter - +%w[redis active_record].each do |adapter| describe Hyperstack::Connection do if adapter == 'active_record' if ActiveRecord::Base.connection.respond_to? :data_sources @@ -25,6 +23,8 @@ end before(:all) do + require adapter + Hyperstack.configuration do |config| config.connection = { adapter: adapter } end From f8350e8d4135a4887713e168c8ea97d9341b1e2d Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Fri, 6 Dec 2019 16:51:59 -0500 Subject: [PATCH 006/307] Fix redis connection querying, update specs --- ruby/hyper-operation/hyper-operation.gemspec | 1 + .../hyper-operation/transport/connection.rb | 4 + .../transport/connection_adapter/redis.rb | 6 +- .../connection_adapter/redis/connection.rb | 44 +- .../connection_adapter/redis/redis_record.rb | 50 +- .../spec/aaa_run_first/connection_spec.rb | 9 +- .../spec/aaa_run_first/transports_spec.rb | 713 +++++++++--------- ruby/hyper-operation/spec/spec_helper.rb | 6 + 8 files changed, 449 insertions(+), 384 deletions(-) diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index d96b4c48f..49eb2bbe5 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -38,6 +38,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '~> 0.9.4' spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'puma' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb index 47b90aa5b..e4b4e7d28 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection.rb @@ -47,10 +47,14 @@ def open(channel, session = nil, root_path = nil) end def send_to_channel(channel, data) + puts "send_to_channel(#{channel}, #{data})" if show_diagnostics + adapter.send_to_channel(channel, data) end def read(session, root_path) + puts "read(#{session}, #{root_path})" if show_diagnostics + adapter.read(session, root_path) end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb index 7fb330ab5..8b8d4672c 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb @@ -31,7 +31,7 @@ def send_to_channel(channel, data) QueuedMessage.create(connection_id: connection.id, data: data) end - transport.send_data(channel, data) if Connection.exists?(channel: channel, session: '') + transport.send_data(channel, data) if Connection.exists?(channel: channel, session: nil) end def read(session, root_path) @@ -63,7 +63,7 @@ def connect_to_transport(channel, session, root_path) end def disconnect(channel) - Connection.find_by(channel: channel, session: '').each(&:destroy) + Connection.find_by(channel: channel, session: nil).each(&:destroy) end def root_path=(path) @@ -82,7 +82,7 @@ def refresh_connections next_refresh = refresh_started_at + transport.refresh_channels_every channels.each do |channel| - connection = Connection.find_by(channel: channel, session: '') + connection = Connection.find_by(channel: channel, session: nil) connection.update(refresh_at: next_refresh) if connection end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb index 71369e767..d5d3734cc 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/connection.rb @@ -11,10 +11,6 @@ class Connection < RedisRecord::Base attr_accessor(*column_names.map(&:to_sym)) - def messages - QueuedMessage.where(connection_id: id) - end - class << self def transport Hyperstack::Connection.transport @@ -24,7 +20,7 @@ def create(opts = {}) opts.tap do |hash| if opts[:session] hash[:expires_at] = (Time.current + transport.expire_new_connection_in) - else + elsif transport.refresh_channels_every != :never hash[:refresh_at] = (Time.current + transport.refresh_channels_every) end @@ -35,40 +31,52 @@ def create(opts = {}) end def inactive - scope { |id| new(client.hgetall("#{table_name}:#{id}")) if inactive?(id) } + scope { |id| id if inactive?(id) } end def inactive?(id) - client.hget("#{table_name}:#{id}", :session).blank? && - client.hget("#{table_name}:#{id}", :refresh_at).present? && - Time.zone.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.current + get_dejsonized_attribute(id, :session).blank? && + get_dejsonized_attribute(id, :refresh_at).present? && + Time.zone.parse(get_dejsonized_attribute(id, :refresh_at)) < Time.current end def expired - scope { |id| new(client.hgetall("#{table_name}:#{id}")) if expired?(id) } + scope { |id| id if expired?(id) } end def expired?(id) - client.hget("#{table_name}:#{id}", :expires_at).present? && - Time.zone.parse(client.hget("#{table_name}:#{id}", :expires_at)) < Time.current + get_dejsonized_attribute(id, :expires_at).present? && + Time.zone.parse(get_dejsonized_attribute(id, :expires_at)) < Time.current end def pending_for(channel) - scope { |id| new(client.hgetall("#{table_name}:#{id}")) if pending_for?(id, channel) } + scope { |id| id if pending_for?(id, channel) } end def pending_for?(id, channel) - client.hget("#{table_name}:#{id}", :session).present? && - client.hget("#{table_name}:#{id}", :channel) == channel + get_dejsonized_attribute(id, :session).present? && + get_dejsonized_attribute(id, :channel) == channel end def needs_refresh? - scope { |id| new(client.hgetall("#{table_name}:#{id}")) if needs_refresh(id) }.any? + scope { |id| id if needs_refresh(id) }.any? end def needs_refresh(id) - client.hget("#{table_name}:#{id}", :refresh_at).present? && - Time.zone.parse(client.hget("#{table_name}:#{id}", :refresh_at)) < Time.current + get_dejsonized_attribute(id, :refresh_at).present? && + Time.zone.parse(get_dejsonized_attribute(id, :refresh_at)) < Time.current + end + end + + def messages + QueuedMessage.where(connection_id: id) + end + + %i[created_at expires_at refresh_at].each do |attr| + define_method(attr) do + value = instance_variable_get(:"@#{attr}") + + value.is_a?(Time) ? value : Time.zone.parse(value) end end end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb index 411e7039f..d8d1eb192 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb @@ -13,37 +13,41 @@ def client end def scope(&block) - client.smembers(table_name).map(&block).compact + ids = client.smembers(table_name) + + ids = ids.map(&block) if block + + ids.compact.map { |id| instantiate(id) } end def all - scope { |id| new(client.hgetall("#{table_name}:#{id}")) } + scope end def first id = client.smembers(table_name).first - new(client.hgetall("#{table_name}:#{id}")) + instantiate(id) end def last id = client.smembers(table_name).last - new(client.hgetall("#{table_name}:#{id}")) + instantiate(id) end def find(id) return unless client.smembers(table_name).include?(id) - new(client.hgetall("#{table_name}:#{id}")) + instantiate(id) end def find_by(opts) found = nil client.smembers(table_name).each do |id| - unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v.to_s }.include?(false) - found = new(client.hgetall("#{table_name}:#{id}")) + unless opts.map { |k, v| get_dejsonized_attribute(id, k) == v }.include?(false) + found = instantiate(id) break end end @@ -61,15 +65,15 @@ def find_or_create_by(opts = {}) def where(opts = {}) scope do |id| - unless opts.map { |k, v| client.hget("#{table_name}:#{id}", k) == v.to_s }.include?(false) - new(client.hgetall("#{table_name}:#{id}")) + unless opts.map { |k, v| get_dejsonized_attribute(id, k) == v }.include?(false) + id end end end def exists?(opts = {}) - !!client.smembers(table_name).detect do |uuid| - !opts.map { |k, v| client.hget("#{table_name}:#{uuid}", k) == v.to_s }.include?(false) + !!client.smembers(table_name).detect do |id| + !opts.map { |k, v| get_dejsonized_attribute(id, k) == v }.include?(false) end end @@ -86,6 +90,28 @@ def destroy_all true end + + def jsonize_attributes(attrs) + attrs.map do |attr, value| + [attr, value.to_json] + end.to_h + end + + def dejsonize_attributes(attrs) + attrs.map do |attr, value| + [attr, JSON.parse(value)] + end.to_h + end + + protected + + def instantiate(id) + new(dejsonize_attributes(client.hgetall("#{table_name}:#{id}"))) + end + + def get_dejsonized_attribute(id, attr) + JSON.parse(client.hget("#{table_name}:#{id}", attr)) + end end def initialize(opts = {}) @@ -93,7 +119,7 @@ def initialize(opts = {}) end def save - self.class.client.hmset("#{table_name}:#{id}", *attributes) + self.class.client.hmset("#{table_name}:#{id}", *self.class.jsonize_attributes(attributes)) unless self.class.client.smembers(table_name).include?(id) self.class.client.sadd(table_name, id) diff --git a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb index 06501de08..8b775e61e 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/connection_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' -%w[redis active_record].each do |adapter| +%i[redis active_record].each do |adapter| describe Hyperstack::Connection do - if adapter == 'active_record' + if adapter == :active_record if ActiveRecord::Base.connection.respond_to? :data_sources it 'creates the tables (rails 5.x)' do ActiveRecord::Base.connection.data_sources.should include('hyperstack_connections') @@ -23,8 +23,6 @@ end before(:all) do - require adapter - Hyperstack.configuration do |config| config.connection = { adapter: adapter } end @@ -32,9 +30,6 @@ before(:each) do Timecop.return - - described_class.adapter::Connection.destroy_all - described_class.adapter::QueuedMessage.destroy_all end it 'creates the messages queue' do diff --git a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb index c77da4adc..59313fa32 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb @@ -9,411 +9,436 @@ def pusher_credentials nil end -describe "Transport Tests", js: true do - - before(:each) do - stub_const "CreateTestModel", Class.new(Hyperstack::ServerOp) - stub_const "MyControllerOp", Class.new(Hyperstack::ControllerOp) - isomorphic do - class MyControllerOp < Hyperstack::ControllerOp - param :data - dispatch_to { session_channel } - end - class CreateTestModel < Hyperstack::ServerOp - param :test_attribute +%i[redis active_record].each do |adapter| + describe 'Transport Tests', js: true do + before(:all) do + Hyperstack.configuration do |config| + config.connection = { adapter: adapter } end end - on_client do - class TestComponent - include Hyperstack::Component - include Hyperstack::State::Observable - before_mount do - @items = [] - receives(CreateTestModel) { |params| mutate @items += [params.test_attribute] } - receives(MyControllerOp) { |params| mutate @message = params.data } + + before(:each) do + stub_const 'CreateTestModel', Class.new(Hyperstack::ServerOp) + stub_const 'MyControllerOp', Class.new(Hyperstack::ControllerOp) + isomorphic do + class MyControllerOp < Hyperstack::ControllerOp + param :data + dispatch_to { session_channel } end - render(DIV) do - DIV { "#{@items.count} items" } - UL { @items.each { |test_attribute| LI { test_attribute }}} - DIV { @message } + class CreateTestModel < Hyperstack::ServerOp + param :test_attribute end end - end - end - - before(:each) do - ApplicationController.acting_user = nil - # spec_helper resets the policy system after each test so we have to setup - # before each test - #stub_const 'ScopeIt', Class.new - stub_const 'ScopeIt::TestApplicationPolicy', Class.new - ScopeIt::TestApplicationPolicy.class_eval do - regulate_class_connection { !self } - always_dispatch_from(CreateTestModel) - end - size_window(:small, :portrait) - on_client do - # patch Hyperstack.connect so it doesn't execute until we say so - # this is NOT used by the polling connection FYI - module Hyperstack - class << self - alias old_connect connect - def go_ahead_and_connect - old_connect(*@connect_args) + on_client do + class TestComponent + include Hyperstack::Component + include Hyperstack::State::Observable + before_mount do + @items = [] + receives(CreateTestModel) { |params| mutate @items += [params.test_attribute] } + receives(MyControllerOp) { |params| mutate @message = params.data } end - def connect(*args) - @connect_args = args + render(DIV) do + DIV { "#{@items.count} items" } + UL { @items.each { |test_attribute| LI { test_attribute } } } + DIV { @message } end end end end - end - - after(:each) do - Timecop.return - wait_for_ajax - end - context "Pusher-Fake" do - before(:all) do - - require 'pusher' - require 'pusher-fake' - Pusher.app_id = "MY_TEST_ID" - Pusher.key = "MY_TEST_KEY" - Pusher.secret = "MY_TEST_SECRET" - require "pusher-fake/support/base" - - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :pusher - config.opts = { - app_id: Pusher.app_id, - key: Pusher.key, - secret: Pusher.secret - }.merge(PusherFake.configuration.web_options) + before(:each) do + ApplicationController.acting_user = nil + # spec_helper resets the policy system after each test so we have to setup + # before each test + # stub_const 'ScopeIt', Class.new + stub_const 'ScopeIt::TestApplicationPolicy', Class.new + ScopeIt::TestApplicationPolicy.class_eval do + regulate_class_connection { !self } + always_dispatch_from(CreateTestModel) + end + size_window(:small, :portrait) + on_client do + # patch Hyperstack.connect so it doesn't execute until we say so + # this is NOT used by the polling connection FYI + module Hyperstack + class << self + alias old_connect connect + def go_ahead_and_connect + old_connect(*@connect_args) + end + def connect(*args) + @connect_args = args + end + end + end end end - it "opens the connection" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - end - - it "will not keep the temporary polled connection open" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) - sleep 1 - end - - it "sees the connection going offline" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - ApplicationController.acting_user = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.refresh_channels_every) - wait_for { Hyperstack::Connection.active }.to eq([]) - end - - it "receives change notifications" do - # one tricky thing about synchromesh is that we want to capture all - # changes to the database that might be made while the client connections - # is still being initialized. To do this we establish a server side - # queue of all messages sent between the time the page begins rendering - # until the connection is established. - - # mount our test component - mount "TestComponent" - # add a model - CreateTestModel.run(test_attribute: "I'm new here!") - # until we connect there should only be 5 items - page.should have_content("0 items") - # okay now we can go ahead and connect (this runs on the client) - evaluate_ruby "Hyperstack.go_ahead_and_connect" - # once we connect it should change to 6 - page.should have_content("1 items") - # now that we are connected the UI should keep updating - CreateTestModel.run(test_attribute: "I'm also new here!") - page.should have_content("2 items") - sleep 1 - end - - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax #rescue nil - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") + after(:each) do + Timecop.return + wait_for_ajax end - end - - context "Action Cable" do - before(:each) do - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :action_cable - config.channel_prefix = "synchromesh" + context 'Pusher-Fake' do + before(:all) do + require 'pusher' + require 'pusher-fake' + Pusher.app_id = 'MY_TEST_ID' + Pusher.key = 'MY_TEST_KEY' + Pusher.secret = 'MY_TEST_SECRET' + require 'pusher-fake/support/base' + + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :pusher + config.opts = { + app_id: Pusher.app_id, + key: Pusher.key, + secret: Pusher.secret + }.merge(PusherFake.configuration.web_options) + end end - end - it "opens the connection" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - end + it 'opens the connection' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + end - it "will not keep the temporary polled connection open" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) - end + it 'will not keep the temporary polled connection open' do + mount 'TestComponent' + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq([]) + sleep 1 + end - it "sees the connection going offline" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - ApplicationController.acting_user = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for { Hyperstack::Connection.active }.to eq([]) - end + it 'sees the connection going offline' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + ApplicationController.acting_user = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.refresh_channels_every) + wait_for { Hyperstack::Connection.active }.to eq([]) + end - it "receives change notifications" do - # one tricky thing about synchromesh is that we want to capture all - # changes to the database that might be made while the client connections - # is still being initialized. To do this we establish a server side - # queue of all messages sent between the time the page begins rendering - # until the connection is established. - - # mount our test component - mount "TestComponent" - # add a model - CreateTestModel.run(test_attribute: "I'm new here!") - # until we connect there should only be 5 items - page.should have_content("0 items") - # okay now we can go ahead and connect (this runs on the client) - evaluate_ruby "Hyperstack.go_ahead_and_connect" - # once we connect it should change to 6 - page.should have_content("1 items") - # now that we are connected the UI should keep updating - CreateTestModel.run(test_attribute: "I'm also new here!") - page.should have_content("2 items") - sleep 1 - end + it 'receives change notifications' do + # one tricky thing about synchromesh is that we want to capture all + # changes to the database that might be made while the client connections + # is still being initialized. To do this we establish a server side + # queue of all messages sent between the time the page begins rendering + # until the connection is established. + + # mount our test component + mount 'TestComponent' + # add a model + CreateTestModel.run(test_attribute: "I'm new here!") + # until we connect there should only be 5 items + page.should have_content('0 items') + # okay now we can go ahead and connect (this runs on the client) + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + # once we connect it should change to 6 + page.should have_content('1 items') + # now that we are connected the UI should keep updating + CreateTestModel.run(test_attribute: "I'm also new here!") + page.should have_content('2 items') + sleep 1 + end - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax rescue nil - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") + it 'broadcasts to the session channel' do + Hyperstack.connect_session = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + wait_for_ajax #rescue nil + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content('hello') + end end - end - context "Real Pusher Account", skip: (pusher_credentials ? false : SKIP_MESSAGE) do + context 'Action Cable' do + before(:each) do + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :action_cable + config.channel_prefix = 'synchromesh' + end + end - before(:each) do - require 'pusher' - Object.send(:remove_const, :PusherFake) if defined?(PusherFake) + it 'opens the connection' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + end - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :pusher - config.opts = pusher_credentials + it 'will not keep the temporary polled connection open' do + mount 'TestComponent' + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq([]) end - end - it "opens the connection" do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - end + it 'sees the connection going offline' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + ApplicationController.acting_user = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + wait_for { Hyperstack::Connection.active }.to eq([]) + end - it "will not keep the temporary polled connection open" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) - end + it 'receives change notifications' do + # one tricky thing about synchromesh is that we want to capture all + # changes to the database that might be made while the client connections + # is still being initialized. To do this we establish a server side + # queue of all messages sent between the time the page begins rendering + # until the connection is established. + + # mount our test component + mount 'TestComponent' + # add a model + CreateTestModel.run(test_attribute: "I'm new here!") + # until we connect there should only be 5 items + page.should have_content('0 items') + # okay now we can go ahead and connect (this runs on the client) + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + # once we connect it should change to 6 + page.should have_content('1 items') + # now that we are connected the UI should keep updating + CreateTestModel.run(test_attribute: "I'm also new here!") + page.should have_content('2 items') + sleep 1 + end - it "sees the connection going offline", skip: 'this keeps failing intermittently, but not due to functional issues' do - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) - ApplicationController.acting_user = true - sleep 1 # needed so Pusher can catch up since its not controlled by timecop - mount "TestComponent" - evaluate_ruby "Hyperstack.go_ahead_and_connect" - Timecop.travel(Time.now+Hyperstack::Connection.transport.refresh_channels_every) - wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) + it 'broadcasts to the session channel' do + Hyperstack.connect_session = true + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + wait_for_ajax rescue nil + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content('hello') + end end - it "receives change notifications" do - # one tricky thing about synchromesh is that we want to capture all - # changes to the database that might be made while the client connections - # is still being initialized. To do this we establish a server side - # queue of all messages sent between the time the page begins rendering - # until the connection is established. - - # mount our test component - mount "TestComponent" - # add a model - CreateTestModel.run(test_attribute: "I'm new here!") - # until we connect there should only be 5 items - page.should have_content("0 items") - # okay now we can go ahead and connect (this runs on the client) - evaluate_ruby "Hyperstack.go_ahead_and_connect" - # once we connect it should change to 6 - page.should have_content("1 items") - # now that we are connected the UI should keep updating - CreateTestModel.run(test_attribute: "I'm also new here!") - page.should have_content("2 items") - sleep 1 - end + context 'Real Pusher Account', skip: (pusher_credentials ? false : SKIP_MESSAGE) do + before(:each) do + require 'pusher' + Object.send(:remove_const, :PusherFake) if defined?(PusherFake) - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - sleep 0.25 - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax rescue nil - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") - end - end + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :pusher + config.opts = pusher_credentials + end + end - context "Simple Polling" do + it 'opens the connection' do + mount 'TestComponent' + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq(['ScopeIt::TestApplication']) + end - before(:all) do - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :simple_poller - # slow down the polling so wait_for_ajax works - config.opts = { seconds_between_poll: 2 } + it 'will not keep the temporary polled connection open' do + mount 'TestComponent' + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active + end.to eq([]) end - end - it "opens the connection" do - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - end + it "sees the connection going offline", skip: 'this keeps failing intermittently, but not due to functional issues' do + mount "TestComponent" + evaluate_ruby "Hyperstack.go_ahead_and_connect" + Timecop.travel(Time.now+Hyperstack::Connection.transport.expire_new_connection_in) + wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq(['ScopeIt::TestApplication']) + ApplicationController.acting_user = true + sleep 1 # needed so Pusher can catch up since its not controlled by timecop + mount "TestComponent" + evaluate_ruby "Hyperstack.go_ahead_and_connect" + Timecop.travel(Time.now+Hyperstack::Connection.transport.refresh_channels_every) + wait_for { sleep 0.25; Hyperstack::Connection.active }.to eq([]) + end - it "sees the connection going offline" do - mount "TestComponent" - wait_for_ajax - ApplicationController.acting_user = true - mount "TestComponent" - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - Timecop.travel(Time.now+Hyperstack.expire_polled_connection_in) - wait(10.seconds).for { Hyperstack::Connection.active }.to eq([]) - end + it "receives change notifications" do + # one tricky thing about synchromesh is that we want to capture all + # changes to the database that might be made while the client connections + # is still being initialized. To do this we establish a server side + # queue of all messages sent between the time the page begins rendering + # until the connection is established. + + # mount our test component + mount "TestComponent" + # add a model + CreateTestModel.run(test_attribute: "I'm new here!") + # until we connect there should only be 5 items + page.should have_content("0 items") + # okay now we can go ahead and connect (this runs on the client) + evaluate_ruby "Hyperstack.go_ahead_and_connect" + # once we connect it should change to 6 + page.should have_content("1 items") + # now that we are connected the UI should keep updating + CreateTestModel.run(test_attribute: "I'm also new here!") + page.should have_content("2 items") + sleep 1 + end - it "receives change notifications" do - mount "TestComponent" - CreateTestModel.run(test_attribute: "I'm new here!") - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] - page.should have_content("1 items") - Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + it "broadcasts to the session channel" do + Hyperstack.connect_session = true + mount "TestComponent" + sleep 0.25 + evaluate_ruby "Hyperstack.go_ahead_and_connect" + wait_for_ajax rescue nil + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content("hello") + end end - it "broadcasts to the session channel" do - Hyperstack.connect_session = true - mount "TestComponent" - sleep 0.25 - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait_for_ajax - evaluate_ruby "MyControllerOp.run(data: 'hello')" - page.should have_content("hello") - end - end + context "Simple Polling" do + before(:all) do + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :simple_poller + # slow down the polling so wait_for_ajax works + config.opts = { seconds_between_poll: 2 } + end + end - context 'Misc Tests' do - it 'has a anti_csrf_token' do - expect_evaluate_ruby('Hyperstack.anti_csrf_token').to be_present - end + it "opens the connection" do + mount "TestComponent" + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + end - it 'wait for an instance channel to be loaded before connecting' do - # Make a pretend mini model, and allow it to be accessed by user-123 - stub_const "UserModelPolicy", Class.new - stub_const "UserModel", Class.new - UserModel.class_eval do - def initialize(id) - @id = id - end - def ==(other) - id == other.id - end - def self.find(id) - new(id) - end - def id - @id.to_s - end + it "sees the connection going offline" do + mount "TestComponent" + wait_for_ajax + ApplicationController.acting_user = true + mount "TestComponent" + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + Timecop.travel(Time.now+Hyperstack.expire_polled_connection_in) + wait(10.seconds).for { Hyperstack::Connection.active }.to eq([]) end - UserModelPolicy.class_eval do - regulate_instance_connections { UserModel.new(123) } + + it "receives change notifications" do + mount "TestComponent" + CreateTestModel.run(test_attribute: "I'm new here!") + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] + page.should have_content("1 items") + Hyperstack::Connection.active.should =~ ['ScopeIt::TestApplication'] end - # During checkin we will run this op. The spec will make sure it runs - isomorphic do - class CheckIn < Hyperstack::ControllerOp - param :id - validate { params.id == '123' } - step { params.id } - end + it "broadcasts to the session channel" do + Hyperstack.connect_session = true + mount "TestComponent" + sleep 0.25 + evaluate_ruby "Hyperstack.go_ahead_and_connect" + wait_for_ajax + evaluate_ruby "MyControllerOp.run(data: 'hello')" + page.should have_content("hello") end + end + context 'Misc Tests' do + it 'has a anti_csrf_token' do + expect_evaluate_ruby('Hyperstack.anti_csrf_token').to be_present + end - on_client do - # Stub the user model on the client - class UserModel + it 'wait for an instance channel to be loaded before connecting' do + # Make a pretend mini model, and allow it to be accessed by user-123 + stub_const "UserModelPolicy", Class.new + stub_const "UserModel", Class.new + UserModel.class_eval do def initialize(id) @id = id end + def ==(other) + id == other.id + end + def self.find(id) + new(id) + end def id @id.to_s end end - # stub the normal Hyperstack::Model.load to call checkin - # note that load returns a promise and so does checkin! Lovely - module Hyperstack - module Model - def self.load - CheckIn.run(id: yield) + UserModelPolicy.class_eval do + regulate_instance_connections { UserModel.new(123) } + end + + # During checkin we will run this op. The spec will make sure it runs + isomorphic do + class CheckIn < Hyperstack::ControllerOp + param :id + validate { params.id == '123' } + step { params.id } + end + end + + on_client do + # Stub the user model on the client + class UserModel + def initialize(id) + @id = id + end + def id + @id.to_s + end + end + # stub the normal Hyperstack::Model.load to call checkin + # note that load returns a promise and so does checkin! Lovely + module Hyperstack + module Model + def self.load + CheckIn.run(id: yield) + end end end end - end - # use action cable - Hyperstack.configuration do |config| - config.connect_session = false - config.transport = :action_cable - config.channel_prefix = "synchromesh" - end + # use action cable + Hyperstack.configuration do |config| + config.connect_session = false + config.transport = :action_cable + config.channel_prefix = 'synchromesh' + end - expect(CheckIn).to receive(:run).and_call_original - evaluate_ruby "Hyperstack.connect(UserModel.new(123))" - # the suite previously redefined connect so we have to call this to initiate - # the connection - evaluate_ruby "Hyperstack.go_ahead_and_connect" - wait(10.seconds).for { Hyperstack::Connection.active }.to eq(['UserModel-123']) + expect(CheckIn).to receive(:run).and_call_original + evaluate_ruby 'Hyperstack.connect(UserModel.new(123))' + # the suite previously redefined connect so we have to call this to initiate + # the connection + evaluate_ruby 'Hyperstack.go_ahead_and_connect' + wait(10.seconds).for { Hyperstack::Connection.active }.to eq(['UserModel-123']) + end end - end end diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index 1706d2cc4..52455f2b2 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -86,6 +86,12 @@ def self.on_server? # end end + config.before(:each) do + if Hyperstack.connection[:adapter] == :redis + Hyperstack::Connection.adapter::RedisRecord::Base.client.flushdb + end + end + config.before(:each, :js => true) do # -sfc george DatabaseCleaner.strategy = :truncation end From 7037b972b2c1af5a8c4a0990fe46cd31aeb4b26a Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 3 Mar 2020 14:54:57 -0500 Subject: [PATCH 007/307] fixes hyperspec to be compatible with latest chrome --- ruby/hyper-spec/lib/hyper-spec.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index e6e9664cb..fd0528757 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -51,13 +51,14 @@ def self.on_server? Capybara.default_max_wait_time = 10 Capybara.register_driver :chrome do |app| + binding.pry options = {} options.merge!( - args: %w[auto-open-devtools-for-tabs], - prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - ) unless ENV['NO_DEBUGGER'] + args: %w[auto-open-devtools-for-tabs]) #, + #prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } + #) unless ENV['NO_DEBUGGER'] # this does not seem to work properly. Don't document this feature yet. - options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] + #options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) end From 27a44d926b35668a2f77aee30db7b42ee481d88b Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 6 Mar 2020 15:18:55 -0500 Subject: [PATCH 008/307] this sort of works --- ruby/hyper-component/hyper-component.gemspec | 2 +- ruby/hyper-operation/hyper-operation.gemspec | 2 +- ruby/hyper-spec/hyper-spec.gemspec | 2 +- ruby/hyper-state/hyper-state.gemspec | 2 +- .../hyperstack-config.gemspec | 2 +- .../lib/hyperstack-loader-application.rb.erb | 23 ++++++++++++++++++- .../lib/hyperstack-loader-system-code.rb.erb | 1 + .../lib/hyperstack-loader.js | 9 ++++---- .../lib/hyperstack/imports.rb | 9 +++++--- ruby/rails-hyperstack/Gemfile | 2 +- ruby/rails-hyperstack/Rakefile | 4 +--- .../rails-hyperstack/rails-hyperstack.gemspec | 6 ++--- 12 files changed, 43 insertions(+), 21 deletions(-) diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index 572e55295..92ee0e7b5 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' + # spec.add_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index 49b95945f..6a141fff0 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -34,7 +34,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'hyper-spec', Hyperstack::Operation::VERSION spec.add_development_dependency 'mysql2' - spec.add_development_dependency 'opal', '>= 0.11.0', '< 0.12.0' + #spec.add_development_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '~> 0.9.4' spec.add_development_dependency 'pry-rescue' diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 22276b5fa..94123edcf 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'method_source' spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' + spec.add_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' spec.add_dependency 'parser', '>= 2.3.3.1' spec.add_dependency 'pry' spec.add_dependency 'rspec-rails' diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index 900f55aaa..4ef654b2d 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency 'hyperstack-config', Hyperstack::State::VERSION - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' + # spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::State::VERSION diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 492126df1..b0c0f2b28 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'listen', '~> 3.0' # for hot loader spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' + spec.add_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_dependency 'uglifier' spec.add_dependency 'websocket' # for hot loader diff --git a/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb b/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb index 43a195f1a..58394fe7a 100644 --- a/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb +++ b/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb @@ -1 +1,22 @@ -<%= Hyperstack.generate_requires(:client, false, __FILE__) %> +<%= puts 'hi there folk3' %> +require 'opal'; puts 'require "opal"' +require 'browser' # CLIENT ONLY +require 'hyperstack-config' +require 'hyperstack/autoloader' +require 'hyperstack/autoloader_starter' +require 'hyper-state' +require 'react_ujs' +#require 'hyper-model' +require 'browser/delay' # CLIENT ONLY +require 'hyper-component' +require 'hyperstack/component/auto-import' +require 'hyper-operation' +require 'hyperstack/router/react-router-source' +require 'hyperstack/hotloader' # CLIENT ONLY +#require 'config/initializers/inflections.rb' +`console.log('pow')` +puts 'hi' +require 'components/hyper_component' +require 'components/app' +# require 'opal_ujs' +# require_tree '.' diff --git a/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb b/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb index 0abb2c7af..8547acaef 100644 --- a/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb +++ b/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb @@ -1 +1,2 @@ +require 'opal'; puts 'required opal first' <%= Hyperstack.generate_requires(:client, true, __FILE__) %> diff --git a/ruby/hyperstack-config/lib/hyperstack-loader.js b/ruby/hyperstack-config/lib/hyperstack-loader.js index 6c91117c1..b5c877e07 100644 --- a/ruby/hyperstack-config/lib/hyperstack-loader.js +++ b/ruby/hyperstack-config/lib/hyperstack-loader.js @@ -1,6 +1,5 @@ -//= require hyperstack-loader-system-code +//= require xclient_only-aee0ca85caacff8b1d51.js +//= require xclient_and_server-bfc7002342dd5ec2dae5.js //= require hyperstack-loader-application -//= require hyperstack-hotloader-config -Opal.load('hyperstack-loader-system-code') -Opal.load('hyperstack-loader-application') -Hyperstack.hotloader(Hyperstack.hotloader.port, Hyperstack.hotloader.ping) +Opal.loaded(OpalLoaded || []) +Opal.require("hyperstack-loader-application") diff --git a/ruby/hyperstack-config/lib/hyperstack/imports.rb b/ruby/hyperstack-config/lib/hyperstack/imports.rb index d129f4020..d584cdd1d 100644 --- a/ruby/hyperstack-config/lib/hyperstack/imports.rb +++ b/ruby/hyperstack-config/lib/hyperstack/imports.rb @@ -70,7 +70,8 @@ def generate_requires(mode, sys, file) elsif kind == :gem r = "require '#{value}' #{client_guard(render_on_server, render_on_client)}" puts " #{r}" - "puts \"#{r}\"; #{r}" + "# puts \"#{r}\"; #{r}" + r else generate_directive(:require, value, file, render_on_server, render_on_client) end @@ -86,7 +87,8 @@ def generate_directive(directive, to, file, render_on_server, render_on_client) end r = "#{directive} '#{(['.'] + ['..'] * gem_path.length + comp_path).join('/')}' #{client_guard(render_on_server, render_on_client)}" puts " #{r}" - "puts \"#{r}\"; #{r}" + "# puts \"#{r}\"; #{r}" + r end def generate_require_tree(path, render_on_server, render_on_client) @@ -98,7 +100,8 @@ def generate_require_tree(path, render_on_server, render_on_client) fname = fname.gsub(/(\.js$)|(\.rb$)|(\.jsx$)/, '') r = "require '#{fname}' #{client_guard(render_on_server, render_on_client)}" puts " #{r}" - "puts \"#{r}\"; #{r}" + "# puts \"#{r}\"; #{r}" + r end end.compact.join("\n") end diff --git a/ruby/rails-hyperstack/Gemfile b/ruby/rails-hyperstack/Gemfile index 01a0028ac..297b83dc8 100644 --- a/ruby/rails-hyperstack/Gemfile +++ b/ruby/rails-hyperstack/Gemfile @@ -6,5 +6,5 @@ gem 'hyper-operation', path: '../hyper-operation' gem 'hyper-model', path: '../hyper-model' gem 'hyper-router', path: '../hyper-router' gem 'hyper-spec', path: '../hyper-spec' -gem 'rails', '~> 5.2' +#gem 'rails', '~> 5.2' gemspec diff --git a/ruby/rails-hyperstack/Rakefile b/ruby/rails-hyperstack/Rakefile index 845a68c72..7d6bce298 100644 --- a/ruby/rails-hyperstack/Rakefile +++ b/ruby/rails-hyperstack/Rakefile @@ -20,6 +20,4 @@ namespace :spec do end end -task :default do - -end +task :default => :spec diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 4bd9e0e2f..308005e3b 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -57,7 +57,7 @@ You can control how much of the stack gets installed as well: spec.add_dependency 'hyper-model', Hyperstack::VERSION spec.add_dependency 'hyper-router', Hyperstack::ROUTERVERSION spec.add_dependency 'hyperstack-config', Hyperstack::VERSION - spec.add_dependency 'opal-rails', '~> 0.9.4' + spec.add_dependency 'opal-rails'#, '~> 1.0' spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' @@ -80,7 +80,7 @@ You can control how much of the stack gets installed as well: #spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4' # was 1.3.6 -- see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'sass-rails', '~> 5.0' # Use Uglifier as compressor for JavaScript assets spec.add_development_dependency 'uglifier', '>= 1.3.0' @@ -88,7 +88,7 @@ You can control how much of the stack gets installed as well: # gem 'mini_racer', platforms: :ruby # Use CoffeeScript for .coffee assets and views - spec.add_development_dependency 'coffee-rails', '~> 4.2' + #spec.add_development_dependency 'coffee-rails', '~> 4.2' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks spec.add_development_dependency 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder From 079c7d4b3cd2a31b7df02a68d11572349bba3f59 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 9 Mar 2020 12:13:17 -0400 Subject: [PATCH 009/307] boots up but now incompatible with rails 5 / opal 0.9 --- .../lib/hyperstack/component/element.rb | 2 +- .../lib/hyperstack/component/event.rb | 2 +- .../active_record/class_methods.rb | 21 ++- .../reactive_record/dummy_value.rb | 8 +- .../lib/hyperstack/router/history.rb | 2 +- .../lib/hyperstack/router/location.rb | 2 +- .../lib/hyperstack/router/match.rb | 2 +- ruby/hyper-router/lib/react/router/history.rb | 2 +- .../lib/hyperstack-config.rb | 1 + .../lib/hyperstack-loader-application.rb.erb | 23 +-- .../lib/hyperstack-loader-system-code.rb.erb | 1 - .../lib/hyperstack-loader.js | 9 +- .../lib/hyperstack/hotloader/socket.rb | 2 +- .../lib/hyperstack/imports.rb | 19 ++- ruby/rails-hyperstack/.ruby-version | 1 + ruby/rails-hyperstack/Gemfile | 2 +- ruby/rails-hyperstack/Rakefile | 23 +-- .../hyperstack/install_generator.rb | 35 +++-- .../hyperstack/install_generator_base.rb | 67 ++++++--- .../install/hyperstack_generator_base.rb | 62 +++++--- ruby/rails-hyperstack/spec/Gemfile | 1 + ruby/rails-hyperstack/spec/Gemfile.lock | 134 ++++++++++++++++++ ruby/rails-hyperstack/spec/gems.rb | 9 ++ 23 files changed, 324 insertions(+), 106 deletions(-) create mode 100644 ruby/rails-hyperstack/.ruby-version create mode 100644 ruby/rails-hyperstack/spec/Gemfile create mode 100644 ruby/rails-hyperstack/spec/Gemfile.lock create mode 100644 ruby/rails-hyperstack/spec/gems.rb diff --git a/ruby/hyper-component/lib/hyperstack/component/element.rb b/ruby/hyper-component/lib/hyperstack/component/element.rb index dd873f233..4b11fe477 100644 --- a/ruby/hyper-component/lib/hyperstack/component/element.rb +++ b/ruby/hyper-component/lib/hyperstack/component/element.rb @@ -15,7 +15,7 @@ module Component # by using method missing # class Element - include Native + include Native::Wrapper alias_native :element_type, :type alias_native :props, :props diff --git a/ruby/hyper-component/lib/hyperstack/component/event.rb b/ruby/hyper-component/lib/hyperstack/component/event.rb index f8e9fe77f..bbf048e5c 100644 --- a/ruby/hyper-component/lib/hyperstack/component/event.rb +++ b/ruby/hyper-component/lib/hyperstack/component/event.rb @@ -1,7 +1,7 @@ module Hyperstack module Component class Event - include Native + include Native::Wrapper alias_native :bubbles, :bubbles alias_native :cancelable, :cancelable alias_native :current_target, :currentTarget diff --git a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb index 86877f7b9..5f92c2b36 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb @@ -1,11 +1,20 @@ module ActiveRecord - module ClassMethods - - alias _new_without_sti_type_cast new - - def new(*args, &block) - _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type + begin + # Opal 0.11 super did not work with new, but new was defined + alias _new_without_sti_type_cast new + def new(*args, &block) + _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type + end + rescue NameError + def self.extended(base) + base.singleton_class.class_eval do + alias_method :_new_without_sti_type_cast, :new + define_method :new do |*args, &block| + _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type + end + end + end end def base_class diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index e2c9b3077..ea3d71e2b 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -158,7 +158,13 @@ def tap alias inspect to_s - `#{self}.$$proto.toString = Opal.Object.$$proto.toString` + %x{ + if (Opal.Object.$$proto) { + #{self}.$$proto.toString = Opal.Object.$$proto.toString + } else { + #{self}.$$prototype.toString = Opal.Object.$$prototype.toString + } + } def to_f notify diff --git a/ruby/hyper-router/lib/hyperstack/router/history.rb b/ruby/hyper-router/lib/hyperstack/router/history.rb index 38bec876d..30644cdee 100644 --- a/ruby/hyper-router/lib/hyperstack/router/history.rb +++ b/ruby/hyper-router/lib/hyperstack/router/history.rb @@ -1,7 +1,7 @@ module Hyperstack module Router class History - include Native + include Native::Wrapper def initialize(native) @native = native diff --git a/ruby/hyper-router/lib/hyperstack/router/location.rb b/ruby/hyper-router/lib/hyperstack/router/location.rb index 814894b0b..def33c3cf 100644 --- a/ruby/hyper-router/lib/hyperstack/router/location.rb +++ b/ruby/hyper-router/lib/hyperstack/router/location.rb @@ -1,7 +1,7 @@ module Hyperstack module Router class Location - include Native + include Native::Wrapper def initialize(native) @native = native diff --git a/ruby/hyper-router/lib/hyperstack/router/match.rb b/ruby/hyper-router/lib/hyperstack/router/match.rb index bc29a3016..8e16ced2d 100644 --- a/ruby/hyper-router/lib/hyperstack/router/match.rb +++ b/ruby/hyper-router/lib/hyperstack/router/match.rb @@ -1,7 +1,7 @@ module Hyperstack module Router class Match - include Native + include Native::Wrapper def initialize(native) @native = native diff --git a/ruby/hyper-router/lib/react/router/history.rb b/ruby/hyper-router/lib/react/router/history.rb index 0808421f9..841c98304 100644 --- a/ruby/hyper-router/lib/react/router/history.rb +++ b/ruby/hyper-router/lib/react/router/history.rb @@ -1,7 +1,7 @@ module React class Router class History - include Native + include Native::Wrapper def self.current new(`History`) diff --git a/ruby/hyperstack-config/lib/hyperstack-config.rb b/ruby/hyperstack-config/lib/hyperstack-config.rb index b8437736c..a10902fe6 100644 --- a/ruby/hyperstack-config/lib/hyperstack-config.rb +++ b/ruby/hyperstack-config/lib/hyperstack-config.rb @@ -5,6 +5,7 @@ def self.naming_convention end end if RUBY_ENGINE == 'opal' + require 'hyperstack/native_wrapper_compatibility' # needs to be early... require 'hyperstack/deprecation_warning' require 'hyperstack/string' require 'hyperstack/client_stubs' diff --git a/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb b/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb index 58394fe7a..43a195f1a 100644 --- a/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb +++ b/ruby/hyperstack-config/lib/hyperstack-loader-application.rb.erb @@ -1,22 +1 @@ -<%= puts 'hi there folk3' %> -require 'opal'; puts 'require "opal"' -require 'browser' # CLIENT ONLY -require 'hyperstack-config' -require 'hyperstack/autoloader' -require 'hyperstack/autoloader_starter' -require 'hyper-state' -require 'react_ujs' -#require 'hyper-model' -require 'browser/delay' # CLIENT ONLY -require 'hyper-component' -require 'hyperstack/component/auto-import' -require 'hyper-operation' -require 'hyperstack/router/react-router-source' -require 'hyperstack/hotloader' # CLIENT ONLY -#require 'config/initializers/inflections.rb' -`console.log('pow')` -puts 'hi' -require 'components/hyper_component' -require 'components/app' -# require 'opal_ujs' -# require_tree '.' +<%= Hyperstack.generate_requires(:client, false, __FILE__) %> diff --git a/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb b/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb index 8547acaef..0abb2c7af 100644 --- a/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb +++ b/ruby/hyperstack-config/lib/hyperstack-loader-system-code.rb.erb @@ -1,2 +1 @@ -require 'opal'; puts 'required opal first' <%= Hyperstack.generate_requires(:client, true, __FILE__) %> diff --git a/ruby/hyperstack-config/lib/hyperstack-loader.js b/ruby/hyperstack-config/lib/hyperstack-loader.js index b5c877e07..842fd49d0 100644 --- a/ruby/hyperstack-config/lib/hyperstack-loader.js +++ b/ruby/hyperstack-config/lib/hyperstack-loader.js @@ -1,5 +1,8 @@ -//= require xclient_only-aee0ca85caacff8b1d51.js -//= require xclient_and_server-bfc7002342dd5ec2dae5.js +//= require hyperstack-loader-system-code //= require hyperstack-loader-application +//= require hyperstack-hotloader-config Opal.loaded(OpalLoaded || []) -Opal.require("hyperstack-loader-application") +Opal.require('hyperstack-loader-system-code') +Opal.loaded(OpalLoaded || []) +Opal.require('hyperstack-loader-application') +Hyperstack.hotloader(Hyperstack.hotloader.port, Hyperstack.hotloader.ping) diff --git a/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb b/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb index 50b06d5e2..4f0644ef1 100644 --- a/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb +++ b/ruby/hyperstack-config/lib/hyperstack/hotloader/socket.rb @@ -16,7 +16,7 @@ def self.supported? Browser.supports? :WebSocket end - include Native + include Native::Wrapper include IO::Writable def on(str, &block) diff --git a/ruby/hyperstack-config/lib/hyperstack/imports.rb b/ruby/hyperstack-config/lib/hyperstack/imports.rb index d584cdd1d..8dd662e9e 100644 --- a/ruby/hyperstack-config/lib/hyperstack/imports.rb +++ b/ruby/hyperstack-config/lib/hyperstack/imports.rb @@ -58,10 +58,18 @@ def add_inflections(sys) [] end + def add_opal(sys) + return [] unless sys + + puts " require 'opal'" + ["require 'opal'; puts \"require 'opal'\""] + end + def generate_requires(mode, sys, file) handle_webpack - (import_list.collect do |value, cancelled, render_on_server, render_on_client, kind| + (add_opal(sys) + import_list.collect do |value, cancelled, render_on_server, render_on_client, kind| next if cancelled + next if value == 'opal' next if (sys && kind == :tree) || (!sys && kind != :tree) next if mode == :client && !render_on_client next if mode == :server && !render_on_server @@ -70,8 +78,7 @@ def generate_requires(mode, sys, file) elsif kind == :gem r = "require '#{value}' #{client_guard(render_on_server, render_on_client)}" puts " #{r}" - "# puts \"#{r}\"; #{r}" - r + "puts \"#{r}\"; #{r}" else generate_directive(:require, value, file, render_on_server, render_on_client) end @@ -87,8 +94,7 @@ def generate_directive(directive, to, file, render_on_server, render_on_client) end r = "#{directive} '#{(['.'] + ['..'] * gem_path.length + comp_path).join('/')}' #{client_guard(render_on_server, render_on_client)}" puts " #{r}" - "# puts \"#{r}\"; #{r}" - r + "puts \"#{r}\"; #{r}" end def generate_require_tree(path, render_on_server, render_on_client) @@ -100,8 +106,7 @@ def generate_require_tree(path, render_on_server, render_on_client) fname = fname.gsub(/(\.js$)|(\.rb$)|(\.jsx$)/, '') r = "require '#{fname}' #{client_guard(render_on_server, render_on_client)}" puts " #{r}" - "# puts \"#{r}\"; #{r}" - r + "puts \"#{r}\"; #{r}" end end.compact.join("\n") end diff --git a/ruby/rails-hyperstack/.ruby-version b/ruby/rails-hyperstack/.ruby-version new file mode 100644 index 000000000..ec1cf33c3 --- /dev/null +++ b/ruby/rails-hyperstack/.ruby-version @@ -0,0 +1 @@ +2.6.3 diff --git a/ruby/rails-hyperstack/Gemfile b/ruby/rails-hyperstack/Gemfile index 297b83dc8..07bb53587 100644 --- a/ruby/rails-hyperstack/Gemfile +++ b/ruby/rails-hyperstack/Gemfile @@ -6,5 +6,5 @@ gem 'hyper-operation', path: '../hyper-operation' gem 'hyper-model', path: '../hyper-model' gem 'hyper-router', path: '../hyper-router' gem 'hyper-spec', path: '../hyper-spec' -#gem 'rails', '~> 5.2' +# #gem 'rails', '~> 5.2' gemspec diff --git a/ruby/rails-hyperstack/Rakefile b/ruby/rails-hyperstack/Rakefile index 7d6bce298..7677d765d 100644 --- a/ruby/rails-hyperstack/Rakefile +++ b/ruby/rails-hyperstack/Rakefile @@ -7,14 +7,21 @@ namespace :spec do task :prepare do Dir.chdir('spec') do sh('rm -rf test_app') - sh('bundle exec rails new test_app') - Dir.chdir('test_app') do - sh('bundle exec rails g hyperstack:install') - sh('mv Gemfile _Gemfile_') - sh('bundle exec rails generate model Sample name:string description:text') - sh('mv app/models/sample.rb app/hyperstack/models/sample.rb') - sh('bundle exec rake db:migrate') - sh('bundle exec rails dev:cache') + Bundler.with_clean_env do + sh('rails -v') + sh('rails _6.0.2.1_ new test_app -T') + end + Bundler.with_clean_env do + Dir.chdir('test_app') do + sh('cat ../gems.rb >> Gemfile') + sh('bundle update') + sh('spring stop') + sh('bundle exec rails g hyperstack:install') + sh('bundle exec rails generate model Sample name:string description:text') + sh('mv app/models/sample.rb app/hyperstack/models/sample.rb') + sh('bundle exec rake db:migrate') + sh('bundle exec rails dev:cache') # not tested yet... + end end end end diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb index a12a614da..769b726e1 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb @@ -9,6 +9,13 @@ class InstallGenerator < Rails::Generators::Base class_option 'webpack-only', type: :boolean class_option 'hyper-model-only', type: :boolean + def add_clexer + gem 'c_lexer' + Bundler.with_clean_env do + run 'bundle update' + end + end + def add_component if skip_adding_component? # normally this is handled by the hyper:component @@ -113,7 +120,7 @@ def install_webpacker def create_policies_directory return if skip_hyper_model? - policy_file = File.join('app', 'policies', 'application_policy.rb') + policy_file = File.join('app', 'policies', 'hyperstack', 'application_policy.rb') unless File.exist? policy_file create_file policy_file, <<-RUBY # #{policy_file} @@ -122,17 +129,19 @@ def create_policies_directory # The following policy will open up full access (but only in development) # The policy system is very flexible and powerful. See the documentation # for complete details. - class Hyperstack::ApplicationPolicy - # Allow any session to connect: - always_allow_connection - # Send all attributes from all public models - regulate_all_broadcasts { |policy| policy.send_all } - # Allow all changes to models - allow_change(to: :all, on: [:create, :update, :destroy]) { true } - # allow remote access to all scopes - i.e. you can count or get a list of ids - # for any scope or relationship - ApplicationRecord.regulate_scope :all - end unless Rails.env.production? + module Hyperstack + class ApplicationPolicy + # Allow any session to connect: + always_allow_connection + # Send all attributes from all public models + regulate_all_broadcasts { |policy| policy.send_all } + # Allow all changes to models + allow_change(to: :all, on: [:create, :update, :destroy]) { true } + # allow remote access to all scopes - i.e. you can count or get a list of ids + # for any scope or relationship + ApplicationRecord.regulate_scope :all + end unless Rails.env.production? + end RUBY end end @@ -183,6 +192,8 @@ def report end say "\n\n" + + warnings.each { |warning| say "#{warning}", :yellow } end private diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb index 7c81806d4..cd9f7eeb9 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb @@ -6,6 +6,10 @@ class Base < Thor::Group protected + def warnings + @warnings ||= [] + end + def create_component_file(template) clear_cache insure_hyperstack_loader_installed @@ -30,26 +34,48 @@ def clear_cache end def insure_hyperstack_loader_installed + hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} application_js = File.join( 'app', 'assets', 'javascripts', 'application.js' ) - require_tree = %r{//=\s+require_tree\s+} - hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} - unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } - if File.foreach(application_js).any? { |l| l =~ require_tree } - inject_into_file 'app/assets/javascripts/application.js', before: require_tree do - "//= require hyperstack-loader\n" + if File.exist? application_js + unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } + require_tree = %r{//=\s+require_tree\s+} + if File.foreach(application_js).any? { |l| l =~ require_tree } + inject_into_file 'app/assets/javascripts/application.js', before: require_tree do + "//= require hyperstack-loader\n" + end + else + warnings << + " ***********************************************************\n"\ + " * Could not add `//= require hyperstack-loader` directive *\n"\ + " * to the app/assets/application.js file. *\n"\ + " * Normally this directive is added just before the *\n"\ + " * `//= require_tree .` directive at the end of the file, *\n"\ + " * but no require_tree directive was found. You need to *\n"\ + " * manually add `//= require hyperstack-loader` to the *\n"\ + " * app/assets/application.js file. *\n"\ + " ***********************************************************\n" + end + end + else + create_file application_js, "//= require hyperstack-loader\n" + warnings << + " ***********************************************************\n"\ + " * Could not find the app/assets/application.js file. *\n"\ + " * We created one for you, and added the *\n"\ + " * `<%= javascript_include_tag 'application' %>` to your *\n"\ + " * `html.erb` files immediately after any *\n"\ + " * `<%= javascript_pack 'application' %>` tags we found. *\n"\ + " ***********************************************************\n" + application_pack_tag = + /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/ + Dir.glob(File.join('app', 'views', '**', '*.erb')) do |file| + if File.foreach(file).any? { |l| l =~ application_pack_tag } + inject_into_file file, after: application_pack_tag do + "\n <%= javascript_include_tag 'application' %>" + end end - else - puts " ***********************************************************\n"\ - " * Could not add `//= require hyperstack-loader` directive *\n"\ - " * to the app/assets/application.js file. *\n"\ - " * Normally this directive is added just before the *\n"\ - " * `//= require_tree .` directive at the end of the file, *\n"\ - " * but no require_tree directive was found. You need to *\n"\ - " * manually add `//= require hyperstack-loader` to the *\n"\ - " * app/assets/application.js file. *\n"\ - " ***********************************************************\n" end end end @@ -73,10 +99,11 @@ def add_to_manifest(manifest, &block) def add_route return unless options['add-route'] if self.components.count > 1 - puts " ***********************************************************\n"\ - " * The add-route option ignored because more than one *\n"\ - " * component is being generated. *\n"\ - " ***********************************************************\n" + warnings << + " ***********************************************************\n"\ + " * The add-route option ignored because more than one *\n"\ + " * component is being generated. *\n"\ + " ***********************************************************\n" return end action_name = (@modules+[@file_name.underscore]).join('__') diff --git a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb index dd39c8875..b8b21cb6d 100644 --- a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb +++ b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb @@ -6,36 +6,61 @@ class Base < Thor::Group protected + def warnings + @warnings ||= [] + end + def clear_cache run 'rm -rf tmp/cache' unless Dir.exists?(File.join('app', 'hyperstack')) end def insure_hyperstack_loader_installed + hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} application_js = File.join( 'app', 'assets', 'javascripts', 'application.js' ) - require_tree = %r{//=\s+require_tree\s+} - hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} - unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } - if File.foreach(application_js).any? { |l| l =~ require_tree } - inject_into_file 'app/assets/javascripts/application.js', before: require_tree do - "//= require hyperstack-loader\n" + if File.exist? application_js + unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } + require_tree = %r{//=\s+require_tree\s+} + if File.foreach(application_js).any? { |l| l =~ require_tree } + inject_into_file 'app/assets/javascripts/application.js', before: require_tree do + "//= require hyperstack-loader\n" + end + else + warnings << + " ***********************************************************\n"\ + " * Could not add `//= require hyperstack-loader` directive *\n"\ + " * to the app/assets/application.js file. *\n"\ + " * Normally this directive is added just before the *\n"\ + " * `//= require_tree .` directive at the end of the file, *\n"\ + " * but no require_tree directive was found. You need to *\n"\ + " * manually add `//= require hyperstack-loader` to the *\n"\ + " * app/assets/application.js file. *\n"\ + " ***********************************************************\n" + end + end + else + create_file application_js, "//= require hyperstack-loader\n" + warnings << + " ***********************************************************\n"\ + " * Could not find the app/assets/application.js file. *\n"\ + " * We created one for you, and added the *\n"\ + " * `<%= javascript_include_tag 'application' %>` to your *\n"\ + " * `html.erb` files immediately after any *\n"\ + " * `<%= javascript_pack 'application' %>` tags we found. *\n"\ + " ***********************************************************\n" + application_pack_tag = + /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/ + Dir.glob(File.join('app', 'views', '**', '*.erb')) do |file| + if File.foreach(file).any? { |l| l =~ application_pack_tag } + inject_into_file file, after: application_pack_tag do + "\n <%= javascript_include_tag 'application' %>" + end end - else - puts " ***********************************************************\n"\ - " * Could not add `//= require hyperstack-loader` directive *\n"\ - " * to the app/assets/application.js file. *\n"\ - " * Normally this directive is added just before the *\n"\ - " * `//= require_tree .` directive at the end of the file, *\n"\ - " * but no require_tree directive was found. You need to *\n"\ - " * manually add `//= require hyperstack-loader` to the *\n"\ - " * app/assets/application.js file. *\n"\ - " ***********************************************************\n" end end end - def insure_base_component_class_exists @component_base_class = options['base-class'] file_name = File.join( @@ -55,7 +80,8 @@ def add_to_manifest(manifest, &block) def add_route return unless options['add-route'] if self.components.count > 1 - puts " ***********************************************************\n"\ + warnings << + " ***********************************************************\n"\ " * The add-route option ignored because more than one *\n"\ " * component is being generated. *\n"\ " ***********************************************************\n" diff --git a/ruby/rails-hyperstack/spec/Gemfile b/ruby/rails-hyperstack/spec/Gemfile new file mode 100644 index 000000000..33f205482 --- /dev/null +++ b/ruby/rails-hyperstack/spec/Gemfile @@ -0,0 +1 @@ +gem 'rails', '~> 6.0' diff --git a/ruby/rails-hyperstack/spec/Gemfile.lock b/ruby/rails-hyperstack/spec/Gemfile.lock new file mode 100644 index 000000000..5d3622983 --- /dev/null +++ b/ruby/rails-hyperstack/spec/Gemfile.lock @@ -0,0 +1,134 @@ +GEM + specs: + actioncable (6.0.2.1) + actionpack (= 6.0.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (6.0.2.1) + actionpack (= 6.0.2.1) + activejob (= 6.0.2.1) + activerecord (= 6.0.2.1) + activestorage (= 6.0.2.1) + activesupport (= 6.0.2.1) + mail (>= 2.7.1) + actionmailer (6.0.2.1) + actionpack (= 6.0.2.1) + actionview (= 6.0.2.1) + activejob (= 6.0.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (6.0.2.1) + actionview (= 6.0.2.1) + activesupport (= 6.0.2.1) + rack (~> 2.0, >= 2.0.8) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.0.2.1) + actionpack (= 6.0.2.1) + activerecord (= 6.0.2.1) + activestorage (= 6.0.2.1) + activesupport (= 6.0.2.1) + nokogiri (>= 1.8.5) + actionview (6.0.2.1) + activesupport (= 6.0.2.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (6.0.2.1) + activesupport (= 6.0.2.1) + globalid (>= 0.3.6) + activemodel (6.0.2.1) + activesupport (= 6.0.2.1) + activerecord (6.0.2.1) + activemodel (= 6.0.2.1) + activesupport (= 6.0.2.1) + activestorage (6.0.2.1) + actionpack (= 6.0.2.1) + activejob (= 6.0.2.1) + activerecord (= 6.0.2.1) + marcel (~> 0.3.1) + activesupport (6.0.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2) + builder (3.2.4) + concurrent-ruby (1.1.6) + crass (1.0.6) + erubi (1.9.0) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.8.2) + concurrent-ruby (~> 1.0) + loofah (2.4.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.4) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + minitest (5.14.0) + nio4r (2.5.2) + nokogiri (1.10.9) + mini_portile2 (~> 2.4.0) + rack (2.2.2) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (6.0.2.1) + actioncable (= 6.0.2.1) + actionmailbox (= 6.0.2.1) + actionmailer (= 6.0.2.1) + actionpack (= 6.0.2.1) + actiontext (= 6.0.2.1) + actionview (= 6.0.2.1) + activejob (= 6.0.2.1) + activemodel (= 6.0.2.1) + activerecord (= 6.0.2.1) + activestorage (= 6.0.2.1) + activesupport (= 6.0.2.1) + bundler (>= 1.3.0) + railties (= 6.0.2.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (6.0.2.1) + actionpack (= 6.0.2.1) + activesupport (= 6.0.2.1) + method_source + rake (>= 0.8.7) + thor (>= 0.20.3, < 2.0) + rake (13.0.1) + sprockets (4.0.0) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (1.0.1) + thread_safe (0.3.6) + tzinfo (1.2.6) + thread_safe (~> 0.1) + websocket-driver (0.7.1) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.4) + zeitwerk (2.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + rails (~> 6.0) + +BUNDLED WITH + 2.0.2 diff --git a/ruby/rails-hyperstack/spec/gems.rb b/ruby/rails-hyperstack/spec/gems.rb new file mode 100644 index 000000000..6cd94d1d6 --- /dev/null +++ b/ruby/rails-hyperstack/spec/gems.rb @@ -0,0 +1,9 @@ + +gem 'rails-hyperstack', path: '../..' +gem 'hyperstack-config', path: '../../../hyperstack-config' +gem 'hyper-state', path: '../../../hyper-state' +gem 'hyper-component', path: '../../../hyper-component' +gem 'hyper-operation', path: '../../../hyper-operation' +gem 'hyper-model', path: '../../../hyper-model' +gem 'hyper-router', path: '../../../hyper-router' +gem 'hyper-spec', path: '../../../hyper-spec' From e255336e57268a189876d626815b60116ecb9d43 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 9 Mar 2020 12:14:56 -0400 Subject: [PATCH 010/307] initial commit --- .../lib/hyperstack/native_wrapper_compatibility.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb diff --git a/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb b/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb new file mode 100644 index 000000000..aeac33b4b --- /dev/null +++ b/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb @@ -0,0 +1,12 @@ +# allows hyperstack to include Native::Wrapper even if running Opal 0.11 +module Native + module Wrapper + def self.included(klass) + if defined? Native::Helpers + klass.extend Native::Helpers + else + klass.include Native + end + end + end +end From eb521783f9616ab9305266b2f3cc30645453fc61 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 18 Mar 2020 15:39:49 -0400 Subject: [PATCH 011/307] hyper-spec upgraded --- ruby/hyper-spec/Gemfile | 1 + ruby/hyper-spec/hyper-spec.gemspec | 6 +++--- ruby/hyper-spec/lib/hyper-spec.rb | 5 +++-- ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb | 5 +++-- ruby/hyper-spec/spec/hyper_spec.rb | 8 ++++++-- .../spec/test_app/app/assets/javascripts/application.js | 7 ++++++- .../test_app/app/assets/javascripts/server_rendering.js | 7 ++++++- .../hyper-spec/spec/test_app/app/views/components/show.rb | 6 ++++++ ruby/hyper-store/hyper-store.gemspec | 2 +- 9 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 ruby/hyper-spec/spec/test_app/app/views/components/show.rb diff --git a/ruby/hyper-spec/Gemfile b/ruby/hyper-spec/Gemfile index c0e97c691..d30e6f364 100644 --- a/ruby/hyper-spec/Gemfile +++ b/ruby/hyper-spec/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' +#gem 'opal', '< 1.0' gem 'hyper-component', path: '../hyper-component' gem 'hyper-store', path: '../hyper-store' gem 'hyper-state', path: '../hyper-state' diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 94123edcf..a2c604aae 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -29,20 +29,20 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'method_source' spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' + spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' spec.add_dependency 'parser', '>= 2.3.3.1' spec.add_dependency 'pry' spec.add_dependency 'rspec-rails' spec.add_dependency 'selenium-webdriver' spec.add_dependency 'timecop', '~> 0.8.1' spec.add_dependency 'uglifier' - spec.add_dependency 'unparser', '>= 0.4.2' # 0.4 is incompatible + spec.add_dependency 'unparser', '>= 0.4.2' spec.add_dependency 'webdrivers' spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] spec.add_development_dependency 'hyper-component', HyperSpec::VERSION spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' spec.add_development_dependency 'rails', '>= 4.0.0' diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index fd0528757..46b0f6841 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -47,11 +47,12 @@ def self.on_server? end # Capybara config -RSpec.configure do |_config| +RSpec.configure do |config| + config.add_setting :wait_for_initialization_time + config.wait_for_initialization_time = 3 Capybara.default_max_wait_time = 10 Capybara.register_driver :chrome do |app| - binding.pry options = {} options.merge!( args: %w[auto-open-devtools-for-tabs]) #, diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index f41fbe740..031c08679 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -5,7 +5,8 @@ require 'method_source' require_relative '../../lib/hyper-spec/time_cop.rb' -Parser::Builders::Default.emit_procarg0 = true +# Parser::Builders::Default.emit_procarg0 = true +# Parser::Builders::Default.emit_arg_inside_procarg0 = true module HyperSpec module ComponentTestHelpers @@ -333,9 +334,9 @@ def size_window(width = nil, height = nil) end width, height = [height, width] if portrait - unless RSpec.configuration.debugger_width Capybara.current_session.current_window.resize_to(1000, 500) + sleep RSpec.configuration.wait_for_initialization_time wait_for_size(1000, 500) inner_width = evaluate_script('window.innerWidth') RSpec.configuration.debugger_width = 1000 - inner_width diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index a8b30dffe..2323236fc 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -1,12 +1,16 @@ require 'spec_helper' describe 'hyper-spec', js: true do + it 'can visit a page' do + visit 'test' + end + it 'will mount a component' do mount "SayHello", name: 'Fred' expect(page).to have_content('Hello there Fred') end - it "can the mount a component defined in mounts code block" do + it "can mount a component defined in mounts code block" do mount 'ShowOff' do class ShowOff include Hyperstack::Component @@ -18,7 +22,7 @@ class ShowOff context "the client_option method" do - it "can rendered server side only" do + it "can render server side only" do client_option render_on: :server_only mount 'SayHello', name: 'George' expect(page).to have_content('Hello there George') diff --git a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js index 26e4d55ab..43638e739 100644 --- a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/application.js @@ -1,4 +1,9 @@ //= require 'react' //= require 'react_ujs' //= require 'components' -Opal.load('components'); +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require("components"); +} diff --git a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js index 29f5acdd8..df7910ca2 100644 --- a/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js +++ b/ruby/hyper-spec/spec/test_app/app/assets/javascripts/server_rendering.js @@ -1,4 +1,9 @@ //= require 'react-server' //= require 'react_ujs' //= require 'components' -Opal.load('components') \ No newline at end of file +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require("components"); +} diff --git a/ruby/hyper-spec/spec/test_app/app/views/components/show.rb b/ruby/hyper-spec/spec/test_app/app/views/components/show.rb new file mode 100644 index 000000000..fa78d4c01 --- /dev/null +++ b/ruby/hyper-spec/spec/test_app/app/views/components/show.rb @@ -0,0 +1,6 @@ +class Show + include Hyperstack::Component + render(DIV) do + "Hello There From A Controller" + end +end diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index a1955b58a..2b0f80f69 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', Hyperstack::Legacy::Store::VERSION spec.add_dependency 'hyper-state', Hyperstack::Legacy::Store::VERSION - spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' + spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::Legacy::Store::VERSION From 42634a64cf3e21fa2423a25a5fa8b13c0233d6d1 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 18 Mar 2020 16:59:36 -0400 Subject: [PATCH 012/307] hyper-state specs pass --- ruby/hyper-spec/hyper-spec.gemspec | 2 +- ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb | 4 ++-- ruby/hyper-state/hyper-state.gemspec | 5 ++--- ruby/hyper-state/spec/spec_helper.rb | 1 - .../spec/test_app/app/assets/javascripts/application.js | 9 +++++++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index a2c604aae..23a3c8c2f 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'method_source' spec.add_dependency 'mini_racer', '~> 0.2.6' spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' - spec.add_dependency 'parser', '>= 2.3.3.1' + spec.add_dependency 'parser', '>= 2.4' spec.add_dependency 'pry' spec.add_dependency 'rspec-rails' spec.add_dependency 'selenium-webdriver' diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 031c08679..1a1876077 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -5,8 +5,8 @@ require 'method_source' require_relative '../../lib/hyper-spec/time_cop.rb' -# Parser::Builders::Default.emit_procarg0 = true -# Parser::Builders::Default.emit_arg_inside_procarg0 = true +Parser::Builders::Default.emit_procarg0 = true +Parser::Builders::Default.emit_arg_inside_procarg0 = true module HyperSpec module ComponentTestHelpers diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index 4ef654b2d..0b267b1c3 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -22,7 +22,6 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency 'hyperstack-config', Hyperstack::State::VERSION - # spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::State::VERSION @@ -30,7 +29,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'listen' spec.add_development_dependency 'mini_racer', '~> 0.2.4' spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4' spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' @@ -41,7 +40,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3' #, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-state/spec/spec_helper.rb b/ruby/hyper-state/spec/spec_helper.rb index f3e8c2480..c8a23b985 100644 --- a/ruby/hyper-state/spec/spec_helper.rb +++ b/ruby/hyper-state/spec/spec_helper.rb @@ -9,7 +9,6 @@ require 'timecop' require 'hyper-spec' require 'hyper-component' -#require 'hyper-store' require 'hyper-state' diff --git a/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js index c68d83120..cfbe1d9be 100644 --- a/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-state/spec/test_app/app/assets/javascripts/application.js @@ -1,4 +1,9 @@ -//= require 'react' +//= require 'react' //= require 'react_ujs' //= require 'components' -Opal.load('components'); +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require('components'); +} From 8387eddca1ee9adbff5dccf13649ae70455c3ac6 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 18 Mar 2020 17:05:41 -0400 Subject: [PATCH 013/307] hyper-store works with rails 6 and opal 1.0 --- ruby/hyper-store/hyper-store.gemspec | 4 ++-- .../spec/test_app/app/assets/javascripts/application.js | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index 2b0f80f69..9843a7397 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'listen' spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'opal-rails'#, '~> 0.9.4' spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' @@ -42,7 +42,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3' #, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js index c68d83120..cfbe1d9be 100644 --- a/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-store/spec/test_app/app/assets/javascripts/application.js @@ -1,4 +1,9 @@ -//= require 'react' +//= require 'react' //= require 'react_ujs' //= require 'components' -Opal.load('components'); +if (typeof(OpalLoaded)=='undefined') { + Opal.load('components'); +} else { + Opal.loaded(OpalLoaded || []); + Opal.require('components'); +} From 24c9487d02dfa0fc2bf38c2684e9de794ffadd6f Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 18 Mar 2020 17:16:36 -0400 Subject: [PATCH 014/307] hyperstack-config specs passing with rails 6.0 opal 1.0 --- ruby/hyperstack-config/hyperstack-config.gemspec | 4 ++-- .../spec/test_app/app/controllers/application_controller.rb | 2 ++ .../spec/test_app/app/controllers/test_controller.rb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 ruby/hyperstack-config/spec/test_app/app/controllers/application_controller.rb diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index b0c0f2b28..57a1cad69 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -32,13 +32,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] spec.add_development_dependency 'chromedriver-helper' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'opal-rails'#, '~> 0.9.4' spec.add_development_dependency 'pry' spec.add_development_dependency 'puma' spec.add_development_dependency 'rails', '>= 4.0.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec', '~> 3.7.0' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3'#, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyperstack-config/spec/test_app/app/controllers/application_controller.rb b/ruby/hyperstack-config/spec/test_app/app/controllers/application_controller.rb new file mode 100644 index 000000000..09705d12a --- /dev/null +++ b/ruby/hyperstack-config/spec/test_app/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb b/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb index f9a0379e2..00ae271ac 100644 --- a/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb +++ b/ruby/hyperstack-config/spec/test_app/app/controllers/test_controller.rb @@ -1,4 +1,4 @@ -class TestController < ActionController::Base +class TestController < ApplicationController def app render inline: 'hello', :layout => "application" end From 2117087f31b746caafb0d72cd4c55f86156ea6d0 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 6 Apr 2020 15:18:58 -0400 Subject: [PATCH 015/307] WIP working through getting hyper-component specs to pass --- ruby/hyper-component/hyper-component.gemspec | 6 +++--- ruby/hyper-component/spec/client_features/base_spec.rb | 1 + ruby/hyper-component/spec/spec_helper.rb | 2 +- ruby/hyper-spec/lib/hyper-spec.rb | 9 ++++++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index 92ee0e7b5..45a612ba5 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -37,8 +37,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'mime-types' spec.add_development_dependency 'nokogiri' spec.add_development_dependency 'opal-jquery' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'opal-rspec' + spec.add_development_dependency 'opal-rails'#, '~> 0.9.4' + #spec.add_development_dependency 'opal-rspec' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' @@ -47,6 +47,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3'#, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-component/spec/client_features/base_spec.rb b/ruby/hyper-component/spec/client_features/base_spec.rb index 826425932..848deb115 100644 --- a/ruby/hyper-component/spec/client_features/base_spec.rb +++ b/ruby/hyper-component/spec/client_features/base_spec.rb @@ -18,6 +18,7 @@ class Foo < HyperComponent it 'can create a simple component class' do mount 'Foo' expect(page.body[-50..-19]).to match(/working<\/span>/) + binding.pry end it 'can create a simple component class that can be inherited to create another component class' do diff --git a/ruby/hyper-component/spec/spec_helper.rb b/ruby/hyper-component/spec/spec_helper.rb index 7e077b653..8ba43b259 100644 --- a/ruby/hyper-component/spec/spec_helper.rb +++ b/ruby/hyper-component/spec/spec_helper.rb @@ -1,7 +1,7 @@ ENV["RAILS_ENV"] ||= 'test' require 'opal' -require 'opal-rspec' +#require 'opal-rspec' require 'opal-jquery' begin diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 46b0f6841..4d11e34bd 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -55,12 +55,19 @@ def self.on_server? Capybara.register_driver :chrome do |app| options = {} options.merge!( + w3c: false, args: %w[auto-open-devtools-for-tabs]) #, #prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } #) unless ENV['NO_DEBUGGER'] # this does not seem to work properly. Don't document this feature yet. #options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options, 'goog:loggingPrefs' => {browser: 'ALL'}) + # Capybara::Selenium::Driver.new(app, :browser => :chrome, desired_capabilities: { + # "chromeOptions" => { + # w3c: false + # }, + # 'goog:loggingPrefs' => {browser: 'ALL'} + # }) Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) end From 0b0c2109b83b0f45de255e1bdfa24f13111d7b90 Mon Sep 17 00:00:00 2001 From: adamcreekroad Date: Thu, 29 Oct 2020 12:41:16 -0400 Subject: [PATCH 016/307] Fixes issue #322, specs included --- ruby/hyper-state/lib/hyperstack/state/observable.rb | 4 ++-- ruby/hyper-state/spec/api/observable_spec.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-state/lib/hyperstack/state/observable.rb b/ruby/hyper-state/lib/hyperstack/state/observable.rb index 716600a8f..71ad04aac 100644 --- a/ruby/hyper-state/lib/hyperstack/state/observable.rb +++ b/ruby/hyper-state/lib/hyperstack/state/observable.rb @@ -12,13 +12,13 @@ def self.included(base) Internal::Receiver.mount(self, *args, &block) end base.send(:"define_#{kind}", :observe) do |*args, &block| - result = block && block.call || args.last + result = block ? block.call : args.last Internal::State::Mapper.observed! self result end base.send(:"define_#{kind}", :mutate) do |*args, &block| # any args will be ignored thus allowing us to say `mutate @foo = 123, @bar[:x] = 7` etc - result = block && block.call || args.last + result = block ? block.call : args.last Internal::State::Mapper.mutated! self result end diff --git a/ruby/hyper-state/spec/api/observable_spec.rb b/ruby/hyper-state/spec/api/observable_spec.rb index 855d8ee54..6a473b130 100644 --- a/ruby/hyper-state/spec/api/observable_spec.rb +++ b/ruby/hyper-state/spec/api/observable_spec.rb @@ -14,6 +14,11 @@ class Store store.instance_eval { @var = 12 } expect(store.instance_eval { observe { @var } }).to eq(12) end + it "can be passed a block that returns a falsy value" do + expect(Hyperstack::Internal::State::Mapper).to receive(:observed!).with(store) + store.instance_eval { @var = false } + expect(store.instance_eval { observe { @var } }).to eq(false) + end it "can be passed args" do expect(Hyperstack::Internal::State::Mapper).to receive(:observed!).with(store) store.instance_eval { @var = 12 } @@ -30,6 +35,10 @@ class Store expect(Hyperstack::Internal::State::Mapper).to receive(:mutated!).with(store) expect(store.instance_eval { mutate { @var = 12 } }).to eq(12) end + it "can be passed a block that returns a falsy value" do + expect(Hyperstack::Internal::State::Mapper).to receive(:mutated!).with(store) + expect(store.instance_eval { mutate { @var = false } }).to eq(false) + end it "can be passed args" do expect(Hyperstack::Internal::State::Mapper).to receive(:mutated!).with(store) expect(store.instance_eval { mutate @other_var = 13, @var = 12 }).to eq(12) From bd0db3ad2bf90b3c8dad5fe0f08aa3b2906ecd2f Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 2 Nov 2020 11:36:36 -0500 Subject: [PATCH 017/307] fixed problem in rails-hyperstack spec --- ruby/hyper-spec/lib/hyper-spec.rb | 1 - ruby/rails-hyperstack/Gemfile | 1 + ruby/rails-hyperstack/rails-hyperstack.gemspec | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index fd0528757..1276c9301 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -51,7 +51,6 @@ def self.on_server? Capybara.default_max_wait_time = 10 Capybara.register_driver :chrome do |app| - binding.pry options = {} options.merge!( args: %w[auto-open-devtools-for-tabs]) #, diff --git a/ruby/rails-hyperstack/Gemfile b/ruby/rails-hyperstack/Gemfile index 01a0028ac..2d8822ef1 100644 --- a/ruby/rails-hyperstack/Gemfile +++ b/ruby/rails-hyperstack/Gemfile @@ -7,4 +7,5 @@ gem 'hyper-model', path: '../hyper-model' gem 'hyper-router', path: '../hyper-router' gem 'hyper-spec', path: '../hyper-spec' gem 'rails', '~> 5.2' +gem 'webpacker' gemspec diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 4bd9e0e2f..8830bec23 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -71,7 +71,7 @@ You can control how much of the stack gets installed as well: # spec.add_development_dependency 'geminabox', '>= 0.13.11' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-spec', Hyperstack::VERSION spec.add_development_dependency 'pry' From 1fd9c531d48128a6adeb670239a6bb6b3bb0dd48 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 2 Nov 2020 12:58:37 -0500 Subject: [PATCH 018/307] removed bundler version from all gems --- ruby/hyper-component/hyper-component.gemspec | 2 +- ruby/hyper-console/hyper-console.gemspec | 2 +- ruby/hyper-i18n/hyper-i18n.gemspec | 2 +- ruby/hyper-model/hyper-model.gemspec | 2 +- ruby/hyper-operation/hyper-operation.gemspec | 2 +- ruby/hyper-router/hyper-router.gemspec | 2 +- ruby/hyper-spec/hyper-spec.gemspec | 2 +- ruby/hyper-state/hyper-state.gemspec | 2 +- ruby/hyper-store/hyper-store.gemspec | 2 +- ruby/hyper-trace/hyper-trace.gemspec | 2 +- ruby/hyperstack-config/hyperstack-config.gemspec | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index 572e55295..882c168e1 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-spec', Hyperstack::Component::VERSION spec.add_development_dependency 'jquery-rails' diff --git a/ruby/hyper-console/hyper-console.gemspec b/ruby/hyper-console/hyper-console.gemspec index 78382b73a..1169dc908 100644 --- a/ruby/hyper-console/hyper-console.gemspec +++ b/ruby/hyper-console/hyper-console.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-operation', Hyperloop::Console::VERSION spec.add_dependency 'hyper-store', Hyperloop::Console::VERSION - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperloop::Console::VERSION spec.add_development_dependency 'hyper-operation', Hyperloop::Console::VERSION diff --git a/ruby/hyper-i18n/hyper-i18n.gemspec b/ruby/hyper-i18n/hyper-i18n.gemspec index 1f440282d..467447fce 100644 --- a/ruby/hyper-i18n/hyper-i18n.gemspec +++ b/ruby/hyper-i18n/hyper-i18n.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-operation', Hyperstack::I18n::VERSION spec.add_dependency 'i18n' - spec.add_development_dependency 'bundler' #, '~> 1.16' + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-model', Hyperstack::I18n::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::I18n::VERSION diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 8f8574817..63ba19b29 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'activerecord', '>= 4.0.0' spec.add_dependency 'hyper-component', HyperModel::VERSION spec.add_dependency 'hyper-operation', HyperModel::VERSION - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'capybara' spec.add_development_dependency 'chromedriver-helper', '1.2.0' spec.add_development_dependency 'libv8', '~> 7.3.492.27.1' diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index 49b95945f..3182e98b0 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'tty-table' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'hyper-spec', Hyperstack::Operation::VERSION diff --git a/ruby/hyper-router/hyper-router.gemspec b/ruby/hyper-router/hyper-router.gemspec index dfbd7180a..82d51508a 100644 --- a/ruby/hyper-router/hyper-router.gemspec +++ b/ruby/hyper-router/hyper-router.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-component', HyperRouter::VERSION spec.add_dependency 'hyper-state', HyperRouter::VERSION spec.add_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'capybara' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'database_cleaner' diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 22276b5fa..9c8f65cb7 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -39,7 +39,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'unparser', '>= 0.4.2' # 0.4 is incompatible spec.add_dependency 'webdrivers' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'hyper-component', HyperSpec::VERSION spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '~> 0.9.4' diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index 900f55aaa..7c5b5de04 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', Hyperstack::State::VERSION spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::State::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::State::VERSION diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index a1955b58a..711e32a8b 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', Hyperstack::Legacy::Store::VERSION spec.add_dependency 'hyper-state', Hyperstack::Legacy::Store::VERSION spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0' - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::Legacy::Store::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::Legacy::Store::VERSION diff --git a/ruby/hyper-trace/hyper-trace.gemspec b/ruby/hyper-trace/hyper-trace.gemspec index f15f87b0e..c4b037fca 100644 --- a/ruby/hyper-trace/hyper-trace.gemspec +++ b/ruby/hyper-trace/hyper-trace.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency 'hyperstack-config', HyperTrace::VERSION - spec.add_development_dependency "bundler", ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency "bundler" spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency "rake", "~> 10.0" end diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 492126df1..936fcefde 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'websocket' # for hot loader - spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1'] + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'opal-rails', '~> 0.9.4' spec.add_development_dependency 'pry' From 9e324adccf01f8c7799e92fde8d365fe3026e231 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 2 Nov 2020 18:05:55 -0500 Subject: [PATCH 019/307] trying to get hypertrace to pass --- ruby/hyper-trace/Gemfile | 1 + ruby/hyper-trace/hyper-trace.gemspec | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ruby/hyper-trace/Gemfile b/ruby/hyper-trace/Gemfile index ac679c0a8..3ef41b3dd 100644 --- a/ruby/hyper-trace/Gemfile +++ b/ruby/hyper-trace/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' gem 'hyperstack-config', path: '../hyperstack-config' +gem 'hyper-spec', path: '../hyper-spec' # Specify your gem's dependencies in hyper-trace.gemspec gemspec diff --git a/ruby/hyper-trace/hyper-trace.gemspec b/ruby/hyper-trace/hyper-trace.gemspec index c4b037fca..34ecd3095 100644 --- a/ruby/hyper-trace/hyper-trace.gemspec +++ b/ruby/hyper-trace/hyper-trace.gemspec @@ -19,7 +19,8 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency 'hyperstack-config', HyperTrace::VERSION + spec.add_development_dependency 'hyper-spec', HyperTrace::VERSION spec.add_development_dependency "bundler" spec.add_development_dependency 'chromedriver-helper' - spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rake" end From 215836b7f38be0edd9a6434c8e107e2f2ab618de Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 16 Nov 2020 16:09:59 -0500 Subject: [PATCH 020/307] closes #325 --- .../active_record/reactive_record/dummy_value.rb | 8 +++++--- .../spec/batch1/column_types/column_type_spec.rb | 12 ++++++++++-- .../spec/test_app/app/models/public/default_test.rb | 13 ++++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index e2c9b3077..909ad33fe 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -55,18 +55,20 @@ def build_default_value_for_date end end + FALSY_VALUES = [false, nil, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"] + def build_default_value_for_boolean - @column_hash[:default] || false + !FALSY_VALUES.include?(@column_hash[:default]) end def build_default_value_for_float - @column_hash[:default] || Float(0.0) + @column_hash[:default]&.to_f || Float(0.0) end alias build_default_value_for_decimal build_default_value_for_float def build_default_value_for_integer - @column_hash[:default] || Integer(0) + @column_hash[:default]&.to_i || Integer(0) end alias build_default_value_for_bigint build_default_value_for_integer diff --git a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb index fc28bf65d..b98181124 100644 --- a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb +++ b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb @@ -297,8 +297,16 @@ class DefaultTest < ActiveRecord::Base r = DefaultTest.create(string: "no no no") expect_evaluate_ruby do t = DefaultTest.find(1) - [t.string, t.date, t.datetime] - end.to eq(["I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json]) + [ + t.string, t.date, t.datetime, t.integer_from_string, t.integer_from_int, + t.float_from_string, t.float_from_float, + t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value + ] + end.to eq([ + "I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json, 99, 98, + 0.02, 0.01, + false, true, false + ]) check_errors end diff --git a/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb b/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb index 0c47a8785..f7433d6dc 100644 --- a/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb +++ b/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb @@ -1,9 +1,16 @@ class DefaultTest < ActiveRecord::Base def self.build_tables connection.create_table :default_tests, force: true do |t| - t.string :string, default: "I'm a string!" - t.date :date, default: Date.today - t.datetime :datetime, default: Time.now + t.string :string, default: "I'm a string!" + t.date :date, default: Date.today + t.datetime :datetime, default: Time.now + t.integer :integer_from_string, default: "99" + t.integer :integer_from_int, default: 98 + t.float :float_from_string, default: "0.02" + t.float :float_from_float, default: 0.01 + t.boolean :boolean_from_falsy_string, default: "OFF" + t.boolean :boolean_from_truthy_string, default: "something-else" + t.boolean :boolean_from_falsy_value, default: false end end end From 31487d0d04746f4c9afec716f98db24b4c6465a3 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 18 Nov 2020 15:40:37 -0500 Subject: [PATCH 021/307] touch to force build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c1df777a..3d2002789 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: bash -cache: +cache: bundler: true directories: - node_modules # NPM packages From cdc3258c5b426595a81bc6021e5d814e75e05a56 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 19 Nov 2020 12:40:09 -0500 Subject: [PATCH 022/307] fixed failing redis adapter spec --- .travis.yml | 2 +- .../lib/hyper-operation/transport/connection_adapter/redis.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3d2002789..4c1df777a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: bash -cache: +cache: bundler: true directories: - node_modules # NPM packages diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb index 8b8d4672c..766e0c389 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis.rb @@ -63,7 +63,7 @@ def connect_to_transport(channel, session, root_path) end def disconnect(channel) - Connection.find_by(channel: channel, session: nil).each(&:destroy) + Connection.find_by(channel: channel, session: nil).destroy end def root_path=(path) From 8e88986ffc76c7be89f0b6617bb8bc5d3b817d23 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 23 Nov 2020 14:53:18 -0500 Subject: [PATCH 023/307] closes issue-326 --- ruby/hyper-model/lib/active_record_base.rb | 6 +++++- .../hyper-operation/lib/hyper-operation/transport/policy.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-model/lib/active_record_base.rb b/ruby/hyper-model/lib/active_record_base.rb index f797f878f..8e79dd4c6 100644 --- a/ruby/hyper-model/lib/active_record_base.rb +++ b/ruby/hyper-model/lib/active_record_base.rb @@ -270,6 +270,10 @@ def denied! Hyperstack::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self.class} regulation denies scope access. Called from #{caller_locations(1)}") end + def saved_changes + previous_changes + end unless method_defined :saved_changes + # call do_not_synchronize to block synchronization of a model def self.do_not_synchronize @@ -297,7 +301,7 @@ def synchromesh_after_create end def synchromesh_after_change - return if do_not_synchronize? || previous_changes.empty? + return if do_not_synchronize? || saved_changes.empty? ReactiveRecord::Broadcast.after_commit :change, self end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb b/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb index 05d9debab..b7dc4c118 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb @@ -467,7 +467,7 @@ def filter(h, attribute_set) def send_message(header, channel, attribute_set, &block) record = filter(@obj.react_serializer, attribute_set) - previous_changes = filter(@obj.previous_changes, attribute_set) + previous_changes = filter(@obj.saved_changes, attribute_set) return if record.empty? && previous_changes.empty? message = header.merge( channel: channel_to_string(channel), From ba229915de69632a37fe6b716334ce9c60485928 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 23 Nov 2020 15:00:02 -0500 Subject: [PATCH 024/307] missing question mark breaks everything --- readme.md | 3 +-- ruby/hyper-model/lib/active_record_base.rb | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index e8fb08cd3..11fd0571c 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,5 @@ # Hyperstack - -[![Build Status](https://travis-ci.org/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.org/hyperstack-org/hyperstack) +[![Build Status](https://travis-ci.com/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.org/hyperstack-org/hyperstack) This is the edge branch - the system is stable, and there are approx 1000 test specs passig. For current status on development see [current status.](https://github.com/hyperstack-org/hyperstack/blob/edge/current-status.md) diff --git a/ruby/hyper-model/lib/active_record_base.rb b/ruby/hyper-model/lib/active_record_base.rb index 8e79dd4c6..d98716c99 100644 --- a/ruby/hyper-model/lib/active_record_base.rb +++ b/ruby/hyper-model/lib/active_record_base.rb @@ -270,9 +270,11 @@ def denied! Hyperstack::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self.class} regulation denies scope access. Called from #{caller_locations(1)}") end - def saved_changes - previous_changes - end unless method_defined :saved_changes + unless method_defined? :saved_changes # for backwards compatibility to Rails < 5.1.7 + def saved_changes + previous_changes + end + end # call do_not_synchronize to block synchronization of a model From 5efd3d6036bd2a13db8cafaa512142e12ad57bfa Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 23 Nov 2020 16:03:24 -0500 Subject: [PATCH 025/307] updated specs to use saved_changes instead of previous_changes --- .../spec/batch1/policies/regulate_all_broadcasts_spec.rb | 4 ++-- .../spec/batch1/policies/regulate_broadcast_spec.rb | 4 ++-- ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb b/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb index 01d06d3d9..2aba5ab49 100644 --- a/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb +++ b/ruby/hyper-model/spec/batch1/policies/regulate_all_broadcasts_spec.rb @@ -9,7 +9,7 @@ def react_serializer as_json # does not include type: xxx as per reactive-record end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end def attribute_names @@ -23,7 +23,7 @@ def attribute_names def react_serializer as_json # does not include type: xxx as per reactive-record end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end def attribute_names diff --git a/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb b/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb index 4deae58c8..c5d412dc8 100644 --- a/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb +++ b/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb @@ -13,7 +13,7 @@ def react_serializer def attribute_names [:id, :attr1, :attr2, :attr3, :attr4, :attr5] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attr1, :attr2, :attr3, :attr4, :attr5 @@ -27,7 +27,7 @@ def react_serializer def attribute_names [:id, :attrA, :attrB, :attrC, :attrD, :attrE] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attrA, :attrB, :attrC, :attrD, :attrE diff --git a/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb b/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb index b46137406..dc1887651 100644 --- a/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb +++ b/ruby/hyper-model/spec/batch1/policies/send_access_xspec.rb @@ -13,7 +13,7 @@ def react_serializer def attribute_names [:id, :attr1, :attr2, :attr3, :attr4, :attr5] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attr1, :attr2, :attr3, :attr4, :attr5 @@ -27,7 +27,7 @@ def react_serializer def attribute_names [:id, :attrA, :attrB, :attrC, :attrD, :attrE] end - def previous_changes + def saved_changes Hash[*as_json.keys.collect { |attr| [attr, send(attr)] }.flatten(1)] end attr_accessor :id, :attrA, :attrB, :attrC, :attrD, :attrE From 6c517d8ca21e82d1247567a8396e30ed684ccc16 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 23 Nov 2020 16:52:00 -0500 Subject: [PATCH 026/307] updated readme to point to travis-ci.com --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 11fd0571c..8dc4390bb 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ # Hyperstack -[![Build Status](https://travis-ci.com/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.org/hyperstack-org/hyperstack) +[![Build Status](https://travis-ci.com/hyperstack-org/hyperstack.svg?branch=edge)](https://travis-ci.com/hyperstack-org/hyperstack) This is the edge branch - the system is stable, and there are approx 1000 test specs passig. For current status on development see [current status.](https://github.com/hyperstack-org/hyperstack/blob/edge/current-status.md) From 3fb51be51c5c1dbf8b1055fe64ab0669a73588eb Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 23 Nov 2020 17:27:40 -0500 Subject: [PATCH 027/307] eased on time precision on delay specs --- ruby/hyper-component/spec/client_features/sleep_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-component/spec/client_features/sleep_spec.rb b/ruby/hyper-component/spec/client_features/sleep_spec.rb index d7515fe42..bda6505fe 100644 --- a/ruby/hyper-component/spec/client_features/sleep_spec.rb +++ b/ruby/hyper-component/spec/client_features/sleep_spec.rb @@ -5,7 +5,7 @@ [1, 3].each do |t| start_time = Time.parse(evaluate_ruby("Time.now")) evaluate_promise "sleep(#{t})" - expect(Time.now-start_time).to be_between(t, t+1) + expect(Time.now-start_time).to be_between(t, t+1.5) end end From 347dcf77fff66255cfa0016af9c2ad4fcebd60cd Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 24 Nov 2020 14:14:24 -0500 Subject: [PATCH 028/307] redis_record needs to handle unset attributes --- .../transport/connection_adapter/redis/redis_record.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb index d8d1eb192..c1c8440d3 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/redis/redis_record.rb @@ -99,7 +99,7 @@ def jsonize_attributes(attrs) def dejsonize_attributes(attrs) attrs.map do |attr, value| - [attr, JSON.parse(value)] + [attr, value && JSON.parse(value)] end.to_h end @@ -110,7 +110,8 @@ def instantiate(id) end def get_dejsonized_attribute(id, attr) - JSON.parse(client.hget("#{table_name}:#{id}", attr)) + value = client.hget("#{table_name}:#{id}", attr) + JSON.parse(value) if value end end From 5af4e54205bfc0010b70c42f5c7a12be8acb31f1 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 11 Dec 2020 16:18:24 -0500 Subject: [PATCH 029/307] forcing build to restart due to ci issue --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 8dc4390bb..e58b98bbc 100644 --- a/readme.md +++ b/readme.md @@ -87,3 +87,4 @@ Hyperstack is an evolution of [Ruby-Hyperloop](https://github.com/ruby-hyperloop + Old Github: https://github.com/ruby-hyperloop + Legacy branch: https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy + Legacy install script: https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy/install + From de2df680faa60d9b1e75f7de1fc87448e9d680a7 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 23 Dec 2020 17:16:45 -0500 Subject: [PATCH 030/307] updated gemspecs to prevent long running dependency checks --- ruby/hyper-model/hyper-model.gemspec | 10 +++++----- ruby/hyper-router/hyper-router.gemspec | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 63ba19b29..f2ff72b0e 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -30,11 +30,11 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-component', HyperModel::VERSION spec.add_dependency 'hyper-operation', HyperModel::VERSION spec.add_development_dependency 'bundler' - spec.add_development_dependency 'capybara' + spec.add_development_dependency 'capybara', '~> 3.33.0' spec.add_development_dependency 'chromedriver-helper', '1.2.0' spec.add_development_dependency 'libv8', '~> 7.3.492.27.1' spec.add_development_dependency 'mini_racer', '~> 0.2.6' - spec.add_development_dependency 'selenium-webdriver' + spec.add_development_dependency 'selenium-webdriver', '~> 3.142.7' spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'factory_bot_rails' #spec.add_development_dependency 'hyper-spec', HyperModel::VERSION @@ -42,7 +42,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opal-activesupport', '~> 0.3.1' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'parser' + spec.add_development_dependency 'parser', '~> 2.3.3.1' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' @@ -62,8 +62,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' - spec.add_development_dependency 'spring-commands-rspec' + spec.add_development_dependency 'spring-commands-rspec', '~> 1.0.4' spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6' spec.add_development_dependency 'timecop', '~> 0.8.1' - spec.add_development_dependency 'unparser' + spec.add_development_dependency 'unparser', '~> 0.4.2' end diff --git a/ruby/hyper-router/hyper-router.gemspec b/ruby/hyper-router/hyper-router.gemspec index 82d51508a..b07afeba0 100644 --- a/ruby/hyper-router/hyper-router.gemspec +++ b/ruby/hyper-router/hyper-router.gemspec @@ -18,15 +18,15 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-state', HyperRouter::VERSION spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'bundler' - spec.add_development_dependency 'capybara' + # spec.add_development_dependency 'capybara' spec.add_development_dependency 'chromedriver-helper' - spec.add_development_dependency 'database_cleaner' + # spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'hyper-spec', HyperRouter::VERSION spec.add_development_dependency 'hyper-store', HyperRouter::VERSION spec.add_development_dependency 'listen' spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'parser' + # spec.add_development_dependency 'parser' spec.add_development_dependency 'puma' spec.add_development_dependency 'rails', '>= 4.0.0' spec.add_development_dependency 'rake' @@ -36,13 +36,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-mocks' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' - spec.add_development_dependency 'selenium-webdriver' + # spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' - spec.add_development_dependency 'sinatra' - spec.add_development_dependency 'spring-commands-rspec' + # spec.add_development_dependency 'sinatra' + # spec.add_development_dependency 'spring-commands-rspec' spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' - spec.add_development_dependency 'unparser' - spec.add_development_dependency 'webdrivers' + # spec.add_development_dependency 'unparser' # ? + # spec.add_development_dependency 'webdrivers' # ? end From 9c405dca99d4247665506c1153bd2f22fa811b7d Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 23 Dec 2020 23:00:24 -0500 Subject: [PATCH 031/307] removed unneeded version specs --- ruby/hyper-model/hyper-model.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index f2ff72b0e..4a67f0a5e 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -32,9 +32,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler' spec.add_development_dependency 'capybara', '~> 3.33.0' spec.add_development_dependency 'chromedriver-helper', '1.2.0' - spec.add_development_dependency 'libv8', '~> 7.3.492.27.1' + spec.add_development_dependency 'libv8' spec.add_development_dependency 'mini_racer', '~> 0.2.6' - spec.add_development_dependency 'selenium-webdriver', '~> 3.142.7' + spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'factory_bot_rails' #spec.add_development_dependency 'hyper-spec', HyperModel::VERSION From 178cc812ff7bc95b78b00b4af2f9d39eee988b4a Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 9 Jan 2021 19:20:13 -0500 Subject: [PATCH 032/307] restricted gemspecs --- ruby/hyper-component/hyper-component.gemspec | 6 +++--- ruby/hyper-state/hyper-state.gemspec | 6 +++--- ruby/hyperstack-config/hyperstack-config.gemspec | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index e27de27b6..3c50aeb0c 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' + spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' @@ -37,7 +37,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'mime-types' spec.add_development_dependency 'nokogiri' spec.add_development_dependency 'opal-jquery' - spec.add_development_dependency 'opal-rails'#, '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' #spec.add_development_dependency 'opal-rspec' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-rescue' @@ -47,6 +47,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3'#, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index 6cc860cea..fae70b87b 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -29,18 +29,18 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'listen' spec.add_development_dependency 'mini_racer', '~> 0.2.4' spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '>= 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', '>= 5.0.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'rspec', '~> 3.7.0' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3' #, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 8739f2f9d..1611b3b74 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -24,21 +24,21 @@ Gem::Specification.new do |spec| spec.add_dependency 'listen', '~> 3.0' # for hot loader spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' - spec.add_dependency 'opal-browser' # '~> 0.2.0' + spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' + spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_dependency 'uglifier' spec.add_dependency 'websocket' # for hot loader spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' - spec.add_development_dependency 'opal-rails'#, '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry' spec.add_development_dependency 'puma' spec.add_development_dependency 'rails', '>= 4.0.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec', '~> 3.7.0' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3'#, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end From 57a1418086014577258b48aa55346b04990b30b1 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 10 Jan 2021 09:43:05 -0500 Subject: [PATCH 033/307] hyper-operations spec should all pass --- ruby/hyper-component/lib/hyper-component.rb | 1 + .../app/assets/javascripts/application.js | 2 -- ruby/hyper-i18n/hyper-i18n.gemspec | 4 +-- ruby/hyper-operation/hyper-operation.gemspec | 4 +-- .../mutations_client_integration_spec.rb | 1 + ruby/hyper-operation/spec/spec_helper.rb | 8 ++++++ .../app/assets/javascripts/application.js | 5 +--- .../components/say_hello.rb | 0 .../spec/test_app/app/views/components.rb | 12 --------- .../config/initializers/hyperstack.rb | 4 +++ ruby/hyper-router/hyper-router.gemspec | 6 ++--- ruby/hyper-store/hyper-store.gemspec | 4 +-- .../lib/hyperstack/imports.rb | 26 +++++++++++++++---- 13 files changed, 45 insertions(+), 32 deletions(-) rename ruby/hyper-operation/spec/test_app/app/{views => hyperstack}/components/say_hello.rb (100%) delete mode 100644 ruby/hyper-operation/spec/test_app/app/views/components.rb create mode 100644 ruby/hyper-operation/spec/test_app/config/initializers/hyperstack.rb diff --git a/ruby/hyper-component/lib/hyper-component.rb b/ruby/hyper-component/lib/hyper-component.rb index 03cc1e6a0..3fb4667ac 100644 --- a/ruby/hyper-component/lib/hyper-component.rb +++ b/ruby/hyper-component/lib/hyper-component.rb @@ -4,6 +4,7 @@ Hyperstack.js_import 'react/react-source-browser', client_only: true, defines: %w[ReactDOM React] Hyperstack.js_import 'react/react-source-server', server_only: true, defines: 'React' Hyperstack.import 'browser/delay', client_only: true +Hyperstack.import 'browser/interval', client_only: true Hyperstack.js_import 'react_ujs', defines: 'ReactRailsUJS' Hyperstack.import 'hyper-component' # TODO: confirm this does not break anything. Added while converting hyperloop->hyperstack Hyperstack.import 'hyperstack/component/auto-import' # TODO: confirm we can cancel the import diff --git a/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.js index b24bc1d61..50d230086 100644 --- a/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-component/spec/test_app/app/assets/javascripts/application.js @@ -1,3 +1 @@ -//xx= require jquery -//xx= require react-server //= require hyperstack-loader diff --git a/ruby/hyper-i18n/hyper-i18n.gemspec b/ruby/hyper-i18n/hyper-i18n.gemspec index 467447fce..790778bfd 100644 --- a/ruby/hyper-i18n/hyper-i18n.gemspec +++ b/ruby/hyper-i18n/hyper-i18n.gemspec @@ -26,11 +26,11 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-model', Hyperstack::I18n::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::I18n::VERSION - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0.0' spec.add_development_dependency 'pry' spec.add_development_dependency 'puma' spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 end diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index 734421919..2787bbaa0 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -36,7 +36,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'mysql2' #spec.add_development_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'puma' @@ -49,6 +49,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rspec-wait' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb b/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb index ce888509d..a296f2caa 100644 --- a/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb +++ b/ruby/hyper-operation/spec/mutations/mutations_client_integration_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' + describe 'mutation client integration', js: true do it "can load and run the mutation gem on the client" do isomorphic do diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index 52455f2b2..3fe0c454c 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -21,6 +21,14 @@ RSpec.configure do |config| + if config.formatters.empty? + module Hyperstack + def self.log_import(s) + # turn off import logging unless in verbose mode + end + end + end + config.after :each do Rails.cache.clear end diff --git a/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js index 93a6e91c8..843c429fd 100644 --- a/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-operation/spec/test_app/app/assets/javascripts/application.js @@ -1,5 +1,2 @@ -//= require 'react' -//= require 'react_ujs' -//= require 'components' //= require action_cable -Opal.load('components'); +//= require hyperstack-loader diff --git a/ruby/hyper-operation/spec/test_app/app/views/components/say_hello.rb b/ruby/hyper-operation/spec/test_app/app/hyperstack/components/say_hello.rb similarity index 100% rename from ruby/hyper-operation/spec/test_app/app/views/components/say_hello.rb rename to ruby/hyper-operation/spec/test_app/app/hyperstack/components/say_hello.rb diff --git a/ruby/hyper-operation/spec/test_app/app/views/components.rb b/ruby/hyper-operation/spec/test_app/app/views/components.rb deleted file mode 100644 index e80cff6f3..000000000 --- a/ruby/hyper-operation/spec/test_app/app/views/components.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'opal' -#require 'react/react-source-browser' -require 'hyper-component' -if Hyperstack::Component::IsomorphicHelpers.on_opal_client? - require 'browser' - require 'browser/delay' - require 'browser/interval' - require 'hyperstack/pusher' -end -require 'hyper-state' -require 'hyper-operation' -require_tree './components' diff --git a/ruby/hyper-operation/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-operation/spec/test_app/config/initializers/hyperstack.rb new file mode 100644 index 000000000..1d1a370cd --- /dev/null +++ b/ruby/hyper-operation/spec/test_app/config/initializers/hyperstack.rb @@ -0,0 +1,4 @@ +Hyperstack.import 'hyperstack/pusher', client_only: true +Hyperstack.cancel_import 'hyperstack/autoloader' +Hyperstack.cancel_import 'hyperstack/autoloader_starter' +Hyperstack.cancel_import 'config/initializers/inflections.rb' diff --git a/ruby/hyper-router/hyper-router.gemspec b/ruby/hyper-router/hyper-router.gemspec index b07afeba0..392eb6558 100644 --- a/ruby/hyper-router/hyper-router.gemspec +++ b/ruby/hyper-router/hyper-router.gemspec @@ -25,10 +25,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'hyper-store', HyperRouter::VERSION spec.add_development_dependency 'listen' spec.add_development_dependency 'mini_racer', '~> 0.2.6' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0.0' # spec.add_development_dependency 'parser' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', '>= 5.0.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' @@ -41,7 +41,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'shoulda-matchers' # spec.add_development_dependency 'sinatra' # spec.add_development_dependency 'spring-commands-rspec' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' # spec.add_development_dependency 'unparser' # ? # spec.add_development_dependency 'webdrivers' # ? diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index eab27b8d0..2648c18e8 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'listen' spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails'#, '~> 0.9.4' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' @@ -42,7 +42,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' spec.add_development_dependency 'rubocop', '~> 0.51.0' - spec.add_development_dependency 'sqlite3' #, '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 + spec.add_development_dependency 'sqlite3', '~> 1.4.2' spec.add_development_dependency 'timecop', '~> 0.8.1' end diff --git a/ruby/hyperstack-config/lib/hyperstack/imports.rb b/ruby/hyperstack-config/lib/hyperstack/imports.rb index 8dd662e9e..bbcad2745 100644 --- a/ruby/hyperstack-config/lib/hyperstack/imports.rb +++ b/ruby/hyperstack-config/lib/hyperstack/imports.rb @@ -1,6 +1,22 @@ module Hyperstack class << self + + # redefine this method ito avoid logging imports. + # typically in rspec - for example: + # + # if config.formatters.empty? + # module Hyperstack + # def self.log_import(s) + # # turn off import logging + # end + # end + # end + + def log_import(s) + puts s + end + def import_list @import_list ||= [] end @@ -52,7 +68,7 @@ def add_inflections(sys) return [] unless sys ["puts \"require 'config/initializers/inflections.rb'\""] + File.open(Rails.root.join('config', 'initializers', 'inflections.rb'), &:readlines).tap do - puts " require 'config/initializers/inflections.rb'" + log_import " require 'config/initializers/inflections.rb'" end rescue Errno::ENOENT [] @@ -61,7 +77,7 @@ def add_inflections(sys) def add_opal(sys) return [] unless sys - puts " require 'opal'" + log_import " require 'opal'" ["require 'opal'; puts \"require 'opal'\""] end @@ -77,7 +93,7 @@ def generate_requires(mode, sys, file) generate_require_tree(value, render_on_server, render_on_client) elsif kind == :gem r = "require '#{value}' #{client_guard(render_on_server, render_on_client)}" - puts " #{r}" + log_import " #{r}" "puts \"#{r}\"; #{r}" else generate_directive(:require, value, file, render_on_server, render_on_client) @@ -93,7 +109,7 @@ def generate_directive(directive, to, file, render_on_server, render_on_client) comp_path.shift end r = "#{directive} '#{(['.'] + ['..'] * gem_path.length + comp_path).join('/')}' #{client_guard(render_on_server, render_on_client)}" - puts " #{r}" + log_import " #{r}" "puts \"#{r}\"; #{r}" end @@ -105,7 +121,7 @@ def generate_require_tree(path, render_on_server, render_on_client) if fname =~ /(\.js$)|(\.rb$)|(\.jsx$)/ fname = fname.gsub(/(\.js$)|(\.rb$)|(\.jsx$)/, '') r = "require '#{fname}' #{client_guard(render_on_server, render_on_client)}" - puts " #{r}" + log_import " #{r}" "puts \"#{r}\"; #{r}" end end.compact.join("\n") From 509e8501d6a4be517ce676188860b1de4b661de5 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 11 Jan 2021 14:36:12 -0500 Subject: [PATCH 034/307] first few hyper-model specs passing --- ruby/hyper-model/.rspec | 1 - ruby/hyper-model/Gemfile | 2 + ruby/hyper-model/hyper-model.gemspec | 29 +- ruby/hyper-model/lib/hyper-model.rb | 4 +- .../active_record/reactive_record/getters.rb | 6 +- ruby/hyper-model/spec/spec_helper.rb | 672 +++++++----------- .../app/assets/javascripts/application.js | 10 +- .../assets/javascripts/server_rendering.js | 4 - .../components/base_classes.rb | 0 .../{views => hyperstack}/components/show.rb | 0 .../models}/active_record_patch.rb | 0 .../public => hyperstack/models}/address.rb | 0 .../models}/application_record.rb | 0 .../public => hyperstack/models}/bone.rb | 0 .../public => hyperstack/models}/cat.rb | 0 .../models}/child_model.rb | 0 .../public => hyperstack/models}/comment.rb | 0 .../models}/default_test.rb | 0 .../public => hyperstack/models}/dog.rb | 0 .../public => hyperstack/models}/pet.rb | 0 .../models}/scratching_post.rb | 0 .../models}/some_model.rb | 0 .../models}/test_model.rb | 0 .../public => hyperstack/models}/todo.rb | 0 .../public => hyperstack/models}/todo_item.rb | 0 .../public => hyperstack/models}/type_test.rb | 0 .../public => hyperstack/models}/user.rb | 0 .../app/models/_react_public_models.rb | 2 - .../spec/test_app/config/application.rb | 6 +- .../config/initializers/hyperstack.rb | 4 + upgrade-from-opal-0.11-rails-5.md | 19 +- 31 files changed, 307 insertions(+), 452 deletions(-) delete mode 100644 ruby/hyper-model/spec/test_app/app/assets/javascripts/server_rendering.js rename ruby/hyper-model/spec/test_app/app/{views => hyperstack}/components/base_classes.rb (100%) rename ruby/hyper-model/spec/test_app/app/{views => hyperstack}/components/show.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/active_record_patch.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/address.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/application_record.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/bone.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/cat.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/child_model.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/comment.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/default_test.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/dog.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/pet.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/scratching_post.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/some_model.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/test_model.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/todo.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/todo_item.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/type_test.rb (100%) rename ruby/hyper-model/spec/test_app/app/{models/public => hyperstack/models}/user.rb (100%) delete mode 100644 ruby/hyper-model/spec/test_app/app/models/_react_public_models.rb create mode 100644 ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb diff --git a/ruby/hyper-model/.rspec b/ruby/hyper-model/.rspec index 8c18f1abd..4e1e0d2f7 100644 --- a/ruby/hyper-model/.rspec +++ b/ruby/hyper-model/.rspec @@ -1,2 +1 @@ ---format documentation --color diff --git a/ruby/hyper-model/Gemfile b/ruby/hyper-model/Gemfile index 162da81cd..4be049818 100644 --- a/ruby/hyper-model/Gemfile +++ b/ruby/hyper-model/Gemfile @@ -6,5 +6,7 @@ gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-state', path: '../hyper-state' gem 'hyper-component', path: '../hyper-component' gem 'hyper-operation', path: '../hyper-operation' +gem 'hyper-spec', path: '../hyper-spec' +gem 'hyperstack-config', path: '../hyperstack-config' gemspec diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 4a67f0a5e..06f4a3ba6 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -29,29 +29,30 @@ Gem::Specification.new do |spec| spec.add_dependency 'activerecord', '>= 4.0.0' spec.add_dependency 'hyper-component', HyperModel::VERSION spec.add_dependency 'hyper-operation', HyperModel::VERSION + spec.add_dependency 'hyperstack-config', HyperModel::VERSION + spec.add_development_dependency 'bundler' - spec.add_development_dependency 'capybara', '~> 3.33.0' - spec.add_development_dependency 'chromedriver-helper', '1.2.0' - spec.add_development_dependency 'libv8' - spec.add_development_dependency 'mini_racer', '~> 0.2.6' - spec.add_development_dependency 'selenium-webdriver' + # spec.add_development_dependency 'capybara', '~> 3.33.0' + # spec.add_development_dependency 'chromedriver-helper', '1.2.0' + # spec.add_development_dependency 'mini_racer', '~> 0.2.6' + # spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'factory_bot_rails' - #spec.add_development_dependency 'hyper-spec', HyperModel::VERSION + spec.add_development_dependency 'hyper-spec', HyperModel::VERSION spec.add_development_dependency 'mysql2' - spec.add_development_dependency 'opal-activesupport', '~> 0.3.1' - spec.add_development_dependency 'opal-browser', '~> 0.2.0' - spec.add_development_dependency 'opal-rails', '~> 0.9.4' - spec.add_development_dependency 'parser', '~> 2.3.3.1' + # spec.add_development_dependency 'opal-activesupport', '~> 0.3.1' + # spec.add_development_dependency 'opal-browser', '~> 0.2.0' + spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' + # spec.add_development_dependency 'parser', '~> 2.3.3.1' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'puma' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', '>= 5.0.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' - spec.add_development_dependency 'reactrb-rails-generator' + # spec.add_development_dependency 'reactrb-rails-generator' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' spec.add_development_dependency 'rspec-its' @@ -63,7 +64,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' spec.add_development_dependency 'spring-commands-rspec', '~> 1.0.4' - spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6' + spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6' spec.add_development_dependency 'timecop', '~> 0.8.1' - spec.add_development_dependency 'unparser', '~> 0.4.2' + #spec.add_development_dependency 'unparser', '~> 0.4.2' end diff --git a/ruby/hyper-model/lib/hyper-model.rb b/ruby/hyper-model/lib/hyper-model.rb index a8fef3628..93b5e51d0 100644 --- a/ruby/hyper-model/lib/hyper-model.rb +++ b/ruby/hyper-model/lib/hyper-model.rb @@ -1,6 +1,8 @@ require 'set' require 'hyperstack-config' -require 'hyper-component' + +Hyperstack.import 'hyper-model' + if RUBY_ENGINE == 'opal' require 'hyper-operation' require 'active_support' diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb index b60cba33e..9a0a80cbc 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb @@ -37,7 +37,8 @@ def get_primary_key_value def get_server_method(attr, reload = nil) non_relationship_getter_common(attr, reload) do |has_key| - sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + # SPLAT BUG: was sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + sync_ignore_dummy attr, Base.load_from_db(self, *(vector || [nil]), *attr), has_key end end @@ -79,7 +80,8 @@ def non_relationship_getter_common(attr, reload, &block) if new? yield has_key if block elsif on_opal_client? - sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + # SPLAT BUG: was sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key + sync_ignore_dummy attr, Base.load_from_db(self, *(vector || [nil]), *attr), has_key elsif id.present? sync_attribute attr, fetch_by_id(attr) else diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index 264f9d3ee..11fd2acd5 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -1,493 +1,329 @@ -# spec/spec_helper.rb ENV["RAILS_ENV"] ||= 'test' -require 'opal' +require 'hyper-spec' +require 'pry' +require 'opal-browser' -def opal? - RUBY_ENGINE == 'opal' -end -def ruby? - !opal? +begin + require File.expand_path('../test_app/config/environment', __FILE__) +rescue LoadError + puts 'Could not load test application. Please ensure you have run `bundle exec rake test_app`' end +require 'rspec/rails' +require 'timecop' +require "rspec/wait" -if RUBY_ENGINE == 'opal' - #require 'hyper-react' - require File.expand_path('../support/react/spec_helpers', __FILE__) - - module Opal - module RSpec - module AsyncHelpers - module ClassMethods - def rendering(title, &block) - klass = Class.new do - include HyperComponent - - def self.block - @block - end - - def self.name - "dummy class" - end - - def render - instance_eval &self.class.block - end - - def self.should_generate(opts={}, &block) - sself = self - @self.async(@title, opts) do - expect_component_to_eventually(sself, &block) - end - end - - def self.should_immediately_generate(opts={}, &block) - sself = self - @self.it(@title, opts) do - element = build_element sself, {} - context = block.arity > 0 ? self : element - expect((element and context.instance_exec(element, &block))).to be(true) - end - end - - end - klass.instance_variable_set("@block", block) - klass.instance_variable_set("@self", self) - klass.instance_variable_set("@title", "it can render #{title}") - klass - end - end +Dir["./spec/support/**/*.rb"].sort.each { |f| require f } + +RSpec.configure do |config| + + if config.formatters.empty? + module Hyperstack + def self.log_import(s) + # turn off import logging unless in verbose mode end end end - - - RSpec.configure do |config| - config.filter_run_including :opal => true + + config.color = true + config.fail_fast = ENV['FAIL_FAST'] || false + config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures") + config.infer_spec_type_from_file_location! + config.mock_with :rspec + config.raise_errors_for_deprecations! + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, comment the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + config.after :each do + Rails.cache.clear end -end -if RUBY_ENGINE != 'opal' - require 'pry' - require 'opal-browser' - begin - require File.expand_path('../test_app/config/environment', __FILE__) - rescue LoadError - puts 'Could not load test application. Please ensure you have run `bundle exec rake test_app`' - end - require 'rspec/rails' - require 'timecop' - require "rspec/wait" - #require 'pusher-fake/support/base' - - Dir["./spec/support/**/*.rb"].sort.each { |f| require f } - - RSpec.configure do |config| - config.color = true - config.fail_fast = ENV['FAIL_FAST'] || false - config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures") - config.infer_spec_type_from_file_location! - config.mock_with :rspec - config.raise_errors_for_deprecations! - - # If you're not using ActiveRecord, or you'd prefer not to run each of your - # examples within a transaction, comment the following line or assign false - # instead of true. - config.use_transactional_fixtures = true - - config.after :each do - Rails.cache.clear - end - - config.after(:each) do |example| - unless example.exception - #Object.send(:remove_const, :Application) rescue nil - ObjectSpace.each_object(Class).each do |klass| - if klass < Hyperstack::Regulation - klass.instance_variables.each { |v| klass.instance_variable_set(v, nil) } - end + config.after(:each) do |example| + unless example.exception + #Object.send(:remove_const, :Application) rescue nil + ObjectSpace.each_object(Class).each do |klass| + if klass < Hyperstack::Regulation + klass.instance_variables.each { |v| klass.instance_variable_set(v, nil) } end - PusherFake::Channel.reset if defined? PusherFake end + PusherFake::Channel.reset if defined? PusherFake end - - config.filter_run_including focus: true - config.filter_run_excluding opal: true - config.run_all_when_everything_filtered = true end - FACTORY_BOT = false + config.filter_run_including focus: true + config.filter_run_excluding opal: true + config.run_all_when_everything_filtered = true +end - #require 'rails_helper' - require 'rspec' - require 'rspec/expectations' - begin - require 'factory_bot_rails' - rescue LoadError - end - require 'shoulda/matchers' - require 'database_cleaner' - require 'capybara/rspec' - require 'capybara/rails' - require 'support/component_helpers' - require 'selenium-webdriver' - - def policy_allows_all - stub_const 'TestApplication', Class.new - stub_const 'TestApplicationPolicy', Class.new - TestApplicationPolicy.class_eval do - always_allow_connection - regulate_all_broadcasts { |policy| policy.send_all } - allow_change(to: :all, on: [:create, :update, :destroy]) { true } - end +FACTORY_BOT = false + +#require 'rails_helper' +require 'rspec' +require 'rspec/expectations' +begin + require 'factory_bot_rails' +rescue LoadError +end +require 'shoulda/matchers' +require 'database_cleaner' +require 'capybara/rspec' +require 'capybara/rails' +# require 'support/component_helpers' +require 'selenium-webdriver' + +def policy_allows_all + stub_const 'TestApplication', Class.new + stub_const 'TestApplicationPolicy', Class.new + TestApplicationPolicy.class_eval do + always_allow_connection + regulate_all_broadcasts { |policy| policy.send_all } + allow_change(to: :all, on: [:create, :update, :destroy]) { true } end +end - module React - module IsomorphicHelpers - def self.xxxload_context(ctx, controller, name = nil) - @context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller, name) - end +module React + module IsomorphicHelpers + def self.xxxload_context(ctx, controller, name = nil) + @context = Context.new("#{controller.object_id}-#{Time.now.to_i}", ctx, controller, name) end end +end - #Capybara.default_max_wait_time = 4.seconds +#Capybara.default_max_wait_time = 4.seconds - Capybara.server = :puma +Capybara.server = :puma - # The following is deprecated and replaced by the above... just make sure it works - # before removing - # Capybara.server { |app, port| - # require 'puma' - # Puma::Server.new(app).tap do |s| - # s.add_tcp_listener Capybara.server_host, port - # end.run.join - # } +# The following is deprecated and replaced by the above... just make sure it works +# before removing +# Capybara.server { |app, port| +# require 'puma' +# Puma::Server.new(app).tap do |s| +# s.add_tcp_listener Capybara.server_host, port +# end.run.join +# } - module WaitForAjax +module WaitForAjax - def wait_for_ajax - Timeout.timeout(Capybara.default_max_wait_time) do - begin - sleep 0.25 - end until finished_all_ajax_requests? - end + def wait_for_ajax + Timeout.timeout(Capybara.default_max_wait_time) do + begin + sleep 0.25 + end until finished_all_ajax_requests? end + end - def running? - jscode = <<-CODE - (function() { - if (typeof Opal !== "undefined" && Opal.Hyperstack !== undefined) { - try { - return Opal.Hyperstack.$const_get("HTTP")["$active?"](); - } catch(err) { - if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { - return jQuery.active > 0; - } + def running? + jscode = <<-CODE + (function() { + if (typeof Opal !== "undefined" && Opal.Hyperstack !== undefined) { + try { + return Opal.Hyperstack.$const_get("HTTP")["$active?"](); + } catch(err) { + if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { + return jQuery.active > 0; } - } else if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { - return jQuery.active > 0; - } else { - return false; } - })(); - CODE - page.evaluate_script(jscode) - rescue Exception => e - puts "wait_for_ajax failed while testing state of jQuery.active: #{e}" - end + } else if (typeof jQuery !== "undefined" && jQuery.active !== undefined) { + return jQuery.active > 0; + } else { + return false; + } + })(); + CODE + page.evaluate_script(jscode) + rescue Exception => e + puts "wait_for_ajax failed while testing state of jQuery.active: #{e}" + end - def finished_all_ajax_requests? - unless running? - sleep 0.25 # this was 1 second, not sure if its necessary to be so long... - !running? - end - rescue Capybara::NotSupportedByDriverError - true - rescue Exception => e - e.message == "jQuery or Hyperstack::HTTP is not defined" + def finished_all_ajax_requests? + unless running? + sleep 0.25 # this was 1 second, not sure if its necessary to be so long... + !running? end - + rescue Capybara::NotSupportedByDriverError + true + rescue Exception => e + e.message == "jQuery or Hyperstack::HTTP is not defined" end - RSpec.configure do |config| - config.include WaitForAjax +end + +module CheckErrors + def check_errors + logs = page.driver.browser.manage.logs.get(:browser) + errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + puts "WARNING - FOUND UNEXPECTED ERRORS #{errors}" if errors.present? end +end - RSpec.configure do |config| - # rspec-expectations config goes here. You can use an alternate - # assertion/expectation library such as wrong or the stdlib/minitest - # assertions if you prefer. - config.expect_with :rspec do |expectations| - # Enable only the newer, non-monkey-patching expect syntax. - # For more details, see: - # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax - expectations.syntax = [:should, :expect] - end +RSpec.configure do |config| + config.include WaitForAjax + config.include CheckErrors +end - # rspec-mocks config goes here. You can use an alternate test double - # library (such as bogus or mocha) by changing the `mock_with` option here. - config.mock_with :rspec do |mocks| - # Enable only the newer, non-monkey-patching expect syntax. - # For more details, see: - # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - mocks.syntax = :expect - - # Prevents you from mocking or stubbing a method that does not exist on - # a real object. This is generally recommended. - mocks.verify_partial_doubles = true - end +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # Enable only the newer, non-monkey-patching expect syntax. + # For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + expectations.syntax = [:should, :expect] + end - config.include FactoryBot::Syntax::Methods if defined? FactoryBot + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Enable only the newer, non-monkey-patching expect syntax. + # For more details, see: + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + mocks.syntax = :expect + + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended. + mocks.verify_partial_doubles = true + end - config.use_transactional_fixtures = false + config.include FactoryBot::Syntax::Methods if defined? FactoryBot - Capybara.default_max_wait_time = 10.seconds + config.use_transactional_fixtures = false - config.before(:suite) do - #DatabaseCleaner.clean_with(:truncation) - Hyperstack.configuration do |config| - config.transport = :simple_poller - end + Capybara.default_max_wait_time = 10.seconds + + config.before(:suite) do + #DatabaseCleaner.clean_with(:truncation) + Hyperstack.configuration do |config| + config.transport = :simple_poller end + end - # config.before(:each) do - # DatabaseCleaner.strategy = :transaction - # end + # config.before(:each) do + # DatabaseCleaner.strategy = :transaction + # end - config.before(:each) do |x| - Hyperstack.class_eval do - def self.on_server? - true - end + config.before(:each) do |x| + Hyperstack.class_eval do + def self.on_server? + true end end + end - config.before(:each) do |ex| - class ActiveRecord::Base - regulate_scope :unscoped - end + config.before(:each) do |ex| + class ActiveRecord::Base + regulate_scope :unscoped end + end - config.before(:each, :js => true) do - DatabaseCleaner.strategy = :truncation - end + config.before(:each, :js => true) do + DatabaseCleaner.strategy = :truncation + end - config.before(:each, :js => true) do - size_window - end + config.before(:each, :js => true) do + size_window + end - config.before(:each) do - DatabaseCleaner.start - end + config.before(:each) do + DatabaseCleaner.start + end - config.after(:each) do |example| - # I am assuming the unless was there just to aid in debug when using pry.rescue - # perhaps it could be on a switch detecting presence of pry.rescue? - #unless example.exception - # Clear session data - Capybara.reset_sessions! - # Rollback transaction - DatabaseCleaner.clean - #end - end + config.after(:each) do |example| + # I am assuming the unless was there just to aid in debug when using pry.rescue + # perhaps it could be on a switch detecting presence of pry.rescue? + #unless example.exception + # Clear session data + Capybara.reset_sessions! + # Rollback transaction + DatabaseCleaner.clean + #end + end - config.after(:all, :js => true) do - #size_window(:default) - end + config.after(:all, :js => true) do + #size_window(:default) + end - config.before(:all) do - # reset this variable so if any specs are setting up models locally - # the correct hash gets sent to the client. - ActiveRecord::Base.instance_variable_set('@public_columns_hash', nil) - class ActiveRecord::Base - class << self - alias original_public_columns_hash public_columns_hash - end + config.before(:all) do + # reset this variable so if any specs are setting up models locally + # the correct hash gets sent to the client. + ActiveRecord::Base.instance_variable_set('@public_columns_hash', nil) + class ActiveRecord::Base + class << self + alias original_public_columns_hash public_columns_hash end - module Hyperstack - def self.on_error(_operation, _err, _params, formatted_error_message) - ::Rails.logger.debug( - "#{formatted_error_message}\n\n" + - Pastel.new.red( - 'To further investigate you may want to add a debugging '\ - 'breakpoint to the on_error method in config/initializers/hyperstack.rb' - ) + end + module Hyperstack + def self.on_error(_operation, _err, _params, formatted_error_message) + ::Rails.logger.debug( + "#{formatted_error_message}\n\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' ) - end + ) end end + end - config.after(:all) do - class ActiveRecord::Base - class << self - alias public_columns_hash original_public_columns_hash - end + config.after(:all) do + class ActiveRecord::Base + class << self + alias public_columns_hash original_public_columns_hash end end + end - config.after(:each, :js => true) do - page.instance_variable_set("@hyper_spec_mounted", false) - end - - # Fail tests on JavaScript errors in Chrome Headless - class JavaScriptError < StandardError; end - - config.after(:each, js: true) do |spec| - logs = page.driver.browser.manage.logs.get(:browser) - if spec.exception - all_messages = logs.select { |e| e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - puts "Javascript client console messages:\n\n" + - all_messages.join("\n\n") if all_messages.present? - end - errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - if client_options[:deprecation_warnings] == :on - warnings = logs.select { |e| e.level == "WARNING" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - puts "\033[0;33;1m\nJavascript client console warnings:\n\n" + warnings.join("\n\n") + "\033[0;30;21m" if warnings.present? - end - if client_options[:raise_on_js_errors] == :show && errors.present? - puts "\033[031m\nJavascript client console errors:\n\n" + errors.join("\n\n") + "\033[0;30;21m" - elsif client_options[:raise_on_js_errors] == :debug && errors.present? - binding.pry - elsif client_options[:raise_on_js_errors] != :off && errors.present? - raise JavaScriptError, errors.join("\n\n") - end - end + config.after(:each, :js => true) do + page.instance_variable_set("@hyper_spec_mounted", false) + end - config.include Capybara::DSL - - # Capybara.register_driver :chrome do |app| - # #caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"excludeSwitches" => [ "ignore-certificate-errors" ]}) - # caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => [ "--window-size=200,200" ]}) - # Capybara::Selenium::Driver.new(app, :browser => :chrome, :desired_capabilities => caps) - # end - - Capybara.register_driver :chromez do |app| - options = {} - options.merge!( - args: %w[auto-open-devtools-for-tabs], - prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - ) unless ENV['NO_DEBUGGER'] - # this does not seem to work properly. Don't document this feature yet. - options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) - Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) - end + # Fail tests on JavaScript errors in Chrome Headless + class JavaScriptError < StandardError; end - Capybara.register_driver :chrome_headless_docker_travis do |app| - caps = Selenium::WebDriver::Remote::Capabilities.chrome(loggingPrefs:{browser: 'ALL'}) - options = ::Selenium::WebDriver::Chrome::Options.new - options.add_argument('--headless') - options.add_argument('--no-sandbox') - options.add_argument('--disable-dev-shm-usage') - Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options, desired_capabilities: caps) + config.after(:each, js: true) do |spec| + logs = page.driver.browser.manage.logs.get(:browser) + if spec.exception + all_messages = logs.select { |e| e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + puts "Javascript client console messages:\n\n" + + all_messages.join("\n\n") if all_messages.present? end - - Capybara.register_driver :selenium_chrome_headless_with_logs do |app| - caps = Selenium::WebDriver::Remote::Capabilities.chrome(loggingPrefs:{browser: 'ALL'}) - browser_options = ::Selenium::WebDriver::Chrome::Options.new() - # browser_options.args << '--some_option' # add whatever browser args and other options you need (--headless, etc) - browser_options.add_argument('--headless') - Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options, desired_capabilities: caps) - # - # - # - # options = ::Selenium::WebDriver::Chrome::Options.new - # options.add_argument('--headless') - # options.add_argument('--no-sandbox') - # options.add_argument('--disable-dev-shm-usage') - # Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options) + errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + if client_options[:deprecation_warnings] == :on + warnings = logs.select { |e| e.level == "WARNING" && e.message.present? } + .map { |m| m.message.gsub(/\\n/, "\n") }.to_a + puts "\033[0;33;1m\nJavascript client console warnings:\n\n" + warnings.join("\n\n") + "\033[0;30;21m" if warnings.present? end - - - class Selenium::WebDriver::Firefox::Profile - - def self.firebug_version - @firebug_version ||= '2.0.13-fx' - end - - def self.firebug_version=(version) - @firebug_version = version - end - - def frame_position - @frame_position ||= 'detached' - end - - def frame_position=(position) - @frame_position = ["left", "right", "top", "detached"].detect do |side| - position && position[0].downcase == side[0] - end || "detached" - end - - def enable_firebug(version = nil) - version ||= Selenium::WebDriver::Firefox::Profile.firebug_version - add_extension(File.expand_path("../bin/firebug-#{version}.xpi", __FILE__)) - - # For some reason, Firebug seems to trigger the Firefox plugin check - # (navigating to https://www.mozilla.org/en-US/plugincheck/ at startup). - # This prevents it. See http://code.google.com/p/selenium/issues/detail?id=4619. - self["extensions.blocklist.enabled"] = false - - # Prevent "Welcome!" tab - self["extensions.firebug.showFirstRunPage"] = false - - # Enable for all sites. - self["extensions.firebug.allPagesActivation"] = "on" - - # Enable all features. - ['console', 'net', 'script'].each do |feature| - self["extensions.firebug.#{feature}.enableSites"] = true - end - - # Closed by default, will open detached. - self["extensions.firebug.framePosition"] = frame_position - self["extensions.firebug.previousPlacement"] = 3 - self["extensions.firebug.defaultPanelName"] = "console" - - # Disable native "Inspect Element" menu item. - self["devtools.inspector.enabled"] = false - self["extensions.firebug.hideDefaultInspector"] = true - end + if client_options[:raise_on_js_errors] == :show && errors.present? + puts "\033[031m\nJavascript client console errors:\n\n" + errors.join("\n\n") + "\033[0;30;21m" + elsif client_options[:raise_on_js_errors] == :debug && errors.present? + binding.pry + elsif client_options[:raise_on_js_errors] != :off && errors.present? + raise JavaScriptError, errors.join("\n\n") end + end - Capybara.register_driver :selenium_with_firebug do |app| - profile = Selenium::WebDriver::Firefox::Profile.new - profile.frame_position = ENV['DRIVER'] && ENV['DRIVER'][2] - profile.enable_firebug - Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile) - end + config.include Capybara::DSL - Capybara.register_driver :chrome do |app| - Capybara::Selenium::Driver.new(app, :browser => :chrome) - end + # Capybara.register_driver :chrome do |app| + # #caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"excludeSwitches" => [ "ignore-certificate-errors" ]}) + # caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => [ "--window-size=200,200" ]}) + # Capybara::Selenium::Driver.new(app, :browser => :chrome, :desired_capabilities => caps) + # end - if ENV['DRIVER'] =~ /^ff/ - Capybara.javascript_driver = :selenium_with_firebug - elsif ENV['DRIVER'] == 'chrome' - Capybara.javascript_driver = :chromez - elsif ENV['DRIVER'] == 'headless' - Capybara.javascript_driver = :selenium_chrome_headless_with_logs #:selenium_chrome_headless - elsif ENV['DRIVER'] == 'travis' - Capybara.javascript_driver = :chrome_headless_docker_travis - else - Capybara.javascript_driver = :selenium_chrome_headless_with_logs #:selenium_chrome_headless - end +end - include ComponentTestHelpers +FactoryBot.define do + sequence :seq_number do |n| + " #{n}" end - FactoryBot.define do - - sequence :seq_number do |n| - " #{n}" - end - - end if defined? FactoryBot - -end +end if defined? FactoryBot diff --git a/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js b/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js index 4cd60b03a..2f767b8b7 100644 --- a/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js +++ b/ruby/hyper-model/spec/test_app/app/assets/javascripts/application.js @@ -1,6 +1,6 @@ -//= require 'react' -//= require 'react_ujs' -//= require 'components' +//xx= require 'react' +//xx= require 'react_ujs' +//xx= require 'components' //= require action_cable -//= require 'hyperstack/pusher' -Opal.load('components'); +//xx= require 'hyperstack/pusher' +//= require hyperstack-loader diff --git a/ruby/hyper-model/spec/test_app/app/assets/javascripts/server_rendering.js b/ruby/hyper-model/spec/test_app/app/assets/javascripts/server_rendering.js deleted file mode 100644 index 29f5acdd8..000000000 --- a/ruby/hyper-model/spec/test_app/app/assets/javascripts/server_rendering.js +++ /dev/null @@ -1,4 +0,0 @@ -//= require 'react-server' -//= require 'react_ujs' -//= require 'components' -Opal.load('components') \ No newline at end of file diff --git a/ruby/hyper-model/spec/test_app/app/views/components/base_classes.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/components/base_classes.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/views/components/base_classes.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/components/base_classes.rb diff --git a/ruby/hyper-model/spec/test_app/app/views/components/show.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/components/show.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/views/components/show.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/components/show.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/active_record_patch.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/active_record_patch.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/active_record_patch.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/active_record_patch.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/address.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/address.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/address.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/address.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/application_record.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/application_record.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/application_record.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/application_record.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/bone.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/bone.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/bone.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/bone.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/cat.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/cat.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/cat.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/cat.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/child_model.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/child_model.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/child_model.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/child_model.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/comment.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/comment.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/comment.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/comment.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/default_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/default_test.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/dog.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/dog.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/dog.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/dog.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/pet.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/pet.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/pet.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/pet.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/scratching_post.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/scratching_post.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/scratching_post.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/scratching_post.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/some_model.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/some_model.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/some_model.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/some_model.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/test_model.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/test_model.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/test_model.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/test_model.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/todo.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/todo.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/todo.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/todo.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/todo_item.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/todo_item.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/todo_item.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/todo_item.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/type_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/type_test.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/public/user.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/user.rb similarity index 100% rename from ruby/hyper-model/spec/test_app/app/models/public/user.rb rename to ruby/hyper-model/spec/test_app/app/hyperstack/models/user.rb diff --git a/ruby/hyper-model/spec/test_app/app/models/_react_public_models.rb b/ruby/hyper-model/spec/test_app/app/models/_react_public_models.rb deleted file mode 100644 index 9e0c5b16f..000000000 --- a/ruby/hyper-model/spec/test_app/app/models/_react_public_models.rb +++ /dev/null @@ -1,2 +0,0 @@ -# app/models/_react_public_models.rb -require_tree './public' if RUBY_ENGINE == 'opal' diff --git a/ruby/hyper-model/spec/test_app/config/application.rb b/ruby/hyper-model/spec/test_app/config/application.rb index 935758929..a9a04996a 100644 --- a/ruby/hyper-model/spec/test_app/config/application.rb +++ b/ruby/hyper-model/spec/test_app/config/application.rb @@ -12,10 +12,9 @@ module TestApp class Application < Rails::Application config.action_cable.allowed_request_origins = [/http\:\/\/127\.0\.0\.1\:[0-9]*/] - config.eager_load_paths += %W(#{config.root}/app/models/public) - config.autoload_paths += %W(#{config.root}/app/models/public) + # config.eager_load_paths += %W(#{config.root}/app/models/public) + # config.autoload_paths += %W(#{config.root}/app/models/public) config.assets.paths << ::Rails.root.join('app', 'models').to_s - config.hyperstack.auto_config = false config.opal.method_missing = true config.opal.optimized_operators = true config.opal.arity_check = false @@ -25,7 +24,6 @@ class Application < Rails::Application config.opal.spec_location = 'spec-opal' config.assets.cache_store = :null_store - config.hyperstack.auto_config = false config.react.server_renderer_options = { files: ['server_rendering.js'] diff --git a/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb new file mode 100644 index 000000000..1d1a370cd --- /dev/null +++ b/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb @@ -0,0 +1,4 @@ +Hyperstack.import 'hyperstack/pusher', client_only: true +Hyperstack.cancel_import 'hyperstack/autoloader' +Hyperstack.cancel_import 'hyperstack/autoloader_starter' +Hyperstack.cancel_import 'config/initializers/inflections.rb' diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index a600cf7bd..19752568a 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -50,7 +50,24 @@ array.each do |e| ------------ search around for references to config.react.server_renderer_options and/or "server_rendering.js" inconsistent... should now default to hyperstack_prerender_loader.js - and more over there is a hyperstack option as well, so the server_renderer file option should match + and more over there is a hyperstack option as well, so the server_renderer file option should match +------------ + +Opal splat works properly in 1.0! + +```ruby +foo(*[1, 2], [[3, 4]]) + +def foo(*x) + # 0.9 x == [1, 2, [3, 4]] + # 1.0 x == [1, 2, [[3, 4]]] +end +``` + +is there a fix that works for both 1.0 and 0.9 ? or do we need a SWITCH??? + +Found this first in hyper-model, so in hyper-model +these are going to be marked with # SPLAT BUG with the old code until things get sorted ------------ Can't inherit directly from ApplicationController::Base in Rails 6.0 ... From f2082c1087b17d9a64c716bfba8b87c2ab3ae780 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 12 Jan 2021 11:29:54 -0500 Subject: [PATCH 035/307] hyper-operation and below passing with opal 1x 011 and Rails 5 and 6 --- ruby/hyper-component/Gemfile | 4 +- ruby/hyper-component/hyper-component.gemspec | 6 +- .../spec/client_features/component_spec.rb | 10 +- ruby/hyper-operation/hyper-operation.gemspec | 5 +- ruby/hyper-router/hyper-router.gemspec | 13 +- ruby/hyper-spec/hyper-spec.gemspec | 8 +- .../lib/hyper-spec/component_test_helpers.rb | 4 +- ruby/hyper-state/hyper-state.gemspec | 6 +- ruby/hyper-store/hyper-store.gemspec | 8 +- ruby/hyperstack-config/Gemfile | 2 +- .../hyperstack-config.gemspec | 14 +- .../native_wrapper_compatibility.rb | 6 +- .../rails-hyperstack/rails-hyperstack.gemspec | 3 +- upgrade-from-opal-0.11-rails-5.md | 137 +++++++++++++++--- 14 files changed, 161 insertions(+), 65 deletions(-) diff --git a/ruby/hyper-component/Gemfile b/ruby/hyper-component/Gemfile index 9f0df939d..d7d242da7 100644 --- a/ruby/hyper-component/Gemfile +++ b/ruby/hyper-component/Gemfile @@ -4,7 +4,9 @@ gem 'hyper-spec', path: '../hyper-spec' gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-store', path: '../hyper-store' gem 'hyper-state', path: '../hyper-state' -gem 'opal-browser', git: 'https://github.com/opal/opal-browser' +unless ENV['OPAL_VERSION']&.match("0.11") + gem 'opal-browser', git: 'https://github.com/opal/opal-browser' +end #gem 'puma', '~> 3.11.0' # As of adding, version 3.12.0 isn't working so we are locking gemspec diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index 3c50aeb0c..49296406d 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -25,7 +25,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' @@ -38,11 +37,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'nokogiri' spec.add_development_dependency 'opal-jquery' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' - #spec.add_development_dependency 'opal-rspec' - spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rails-controller-testing' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-rails' diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index dcca18714..a3725860e 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -661,8 +661,11 @@ def needs_update?(next_params, next_state) EMPTIES.length.times do |j| e1 = EMPTIES[i] @foo.instance_eval do + # semantically check if we are using Opal 1.0 or better + # if so we need to stringify e1 + e1 = `JSON.stringify(e1)` if 24 == `12+12` @native.JS[:state] = - JS.call(:eval, "function bla(){return #{`JSON.stringify(e1)`};}bla();") + JS.call(:eval, "function bla(){return #{e1};}bla();") end return_values << @foo.should_component_update?({}, Hash.new(EMPTIES[j])) end @@ -679,8 +682,11 @@ def needs_update?(next_params, next_state) EMPTIES.length.times do |i| empty = EMPTIES[i] @foo.instance_eval do + # semantically check if we are using Opal 1.0 or better + # if so we need to stringify e1 + empty = `JSON.stringify(empty)` if 24 == `12+12` @native.JS[:state] = - JS.call(:eval, "function bla(){return #{`JSON.stringify(empty)`};}bla();") + JS.call(:eval, "function bla(){return #{empty};}bla();") end return_values << @foo.should_component_update?({}, {foo: 12}) end diff --git a/ruby/hyper-operation/hyper-operation.gemspec b/ruby/hyper-operation/hyper-operation.gemspec index 2787bbaa0..d4bec9065 100644 --- a/ruby/hyper-operation/hyper-operation.gemspec +++ b/ruby/hyper-operation/hyper-operation.gemspec @@ -34,15 +34,14 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'hyper-spec', Hyperstack::Operation::VERSION spec.add_development_dependency 'mysql2' - #spec.add_development_dependency 'opal' #, '>= 0.11.0', '< 0.12.0' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' - spec.add_development_dependency 'pry-byebug' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'redis' diff --git a/ruby/hyper-router/hyper-router.gemspec b/ruby/hyper-router/hyper-router.gemspec index 392eb6558..a7447895c 100644 --- a/ruby/hyper-router/hyper-router.gemspec +++ b/ruby/hyper-router/hyper-router.gemspec @@ -17,18 +17,18 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-component', HyperRouter::VERSION spec.add_dependency 'hyper-state', HyperRouter::VERSION spec.add_dependency 'opal-browser', '~> 0.2.0' + spec.add_development_dependency 'bundler' - # spec.add_development_dependency 'capybara' spec.add_development_dependency 'chromedriver-helper' - # spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'hyper-spec', HyperRouter::VERSION spec.add_development_dependency 'hyper-store', HyperRouter::VERSION spec.add_development_dependency 'listen' spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0.0' - # spec.add_development_dependency 'parser' + spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 5.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' @@ -36,13 +36,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec-mocks' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-steps', '~> 2.1.1' - # spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'shoulda' spec.add_development_dependency 'shoulda-matchers' - # spec.add_development_dependency 'sinatra' - # spec.add_development_dependency 'spring-commands-rspec' spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' - # spec.add_development_dependency 'unparser' # ? - # spec.add_development_dependency 'webdrivers' # ? end diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 22aa740c7..9788999cf 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -29,9 +29,8 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'method_source' spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' - spec.add_dependency 'parser', '>= 2.4' - spec.add_dependency 'pry' + spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' + spec.add_dependency 'parser', '>= 2.3' spec.add_dependency 'rspec-rails' spec.add_dependency 'selenium-webdriver' spec.add_dependency 'timecop', '~> 0.8.1' @@ -44,8 +43,9 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4' spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.3.0', '< 2.5.0' spec.add_development_dependency 'rspec-collection_matchers' diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 49596477d..42451259f 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -6,7 +6,9 @@ require_relative '../../lib/hyper-spec/time_cop.rb' Parser::Builders::Default.emit_procarg0 = true -Parser::Builders::Default.emit_arg_inside_procarg0 = true +if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 + Parser::Builders::Default.emit_arg_inside_procarg0 = true # not available in parser 2.3 +end module HyperSpec module ComponentTestHelpers diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index fae70b87b..3e13f9121 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency 'hyperstack-config', Hyperstack::State::VERSION + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::State::VERSION @@ -30,10 +31,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'mini_racer', '~> 0.2.4' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' - spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 5.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'rspec', '~> 3.7.0' @@ -42,5 +43,4 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' - end diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index 2648c18e8..77a472c9d 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -21,9 +21,9 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'hyperstack-config', Hyperstack::Legacy::Store::VERSION spec.add_dependency 'hyper-state', Hyperstack::Legacy::Store::VERSION - spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' + spec.add_dependency 'hyperstack-config', Hyperstack::Legacy::Store::VERSION + spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-component', Hyperstack::Legacy::Store::VERSION @@ -32,10 +32,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' - spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'rspec', '~> 3.7.0' diff --git a/ruby/hyperstack-config/Gemfile b/ruby/hyperstack-config/Gemfile index 14de4f173..7b292c5a3 100644 --- a/ruby/hyperstack-config/Gemfile +++ b/ruby/hyperstack-config/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' gem 'hyper-spec', path: '../hyper-spec' -gem 'opal-browser', git: 'https://github.com/opal/opal-browser' +# gem 'opal-browser', git: 'https://github.com/opal/opal-browser' gemspec diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 1611b3b74..8ad3af09e 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |spec| spec.version = Hyperstack::Config::VERSION spec.authors = ['Mitch VanDuyn', 'Jan Biedermann'] spec.email = ['mitch@catprint.com', 'jan@kursator.com'] - spec.summary = %q{Provides a single point configuration module for hyperstack gems} + spec.summary = 'Provides a single point configuration module for hyperstack gems' spec.homepage = 'http://ruby-hyperstack.org' spec.license = 'MIT' # spec.metadata = { @@ -17,25 +17,23 @@ Gem::Specification.new do |spec| # } spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - #spec.bindir = 'exe' - #spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables << 'hyperstack-hotloader' spec.require_paths = ['lib'] - spec.add_dependency 'listen', '~> 3.0' # for hot loader + spec.add_dependency 'listen', '~> 3.0' # for hot loader spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'opal', '>= 0.11.0', '< 2.0' + spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_dependency 'uglifier' spec.add_dependency 'websocket' # for hot loader - spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' - spec.add_development_dependency 'pry' + spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' - spec.add_development_dependency 'rails', '>= 4.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec', '~> 3.7.0' spec.add_development_dependency 'rubocop', '~> 0.51.0' diff --git a/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb b/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb index aeac33b4b..fbb775f19 100644 --- a/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb +++ b/ruby/hyperstack-config/lib/hyperstack/native_wrapper_compatibility.rb @@ -2,10 +2,10 @@ module Native module Wrapper def self.included(klass) - if defined? Native::Helpers - klass.extend Native::Helpers - else + if Native.instance_methods.include? :to_n klass.include Native + else + klass.extend Native::Helpers end end end diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index f80db4309..1bb9e7c43 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -63,7 +63,8 @@ You can control how much of the stack gets installed as well: spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_dependency 'mini_racer', '~> 0.2.6' spec.add_dependency 'libv8', '~> 7.3.492.27.1' - spec.add_dependency 'rails', '>= 4.0.0' + spec.add_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' + spec.add_dependency 'opal', ENV['OPAL_VERSION'] if ENV['OPAL_VERSION'] # spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index 19752568a..06977e662 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -1,3 +1,29 @@ +### Progress: + ++ hyper-spec: passing ++ hyperstack-config: passing ++ hyper-state: passing ++ hyper-store: passing ++ hyper-router: passing ++ hyper-component: passing ++ hyper-operation: passing ++ hyper-model: first couple of specs pass, suspect more issues because splat operator is now working properly. +--- +### Multiple Dependency Environments Supported + +set env OPAL_VERSION='~ > 0.11' (for example) and then bundle update +set env RAILS_VERSION='~> 5.0' (for example) and then bundle update + +note that when testing hyper-component with OPAL_VERSION 0.11 you must also set the environment +variable to the same when running the specs (because we DONT want to fetch opal-browser from github see below) + +note that Opal 0.11 will NOT work with rails 6.x, so that leaves three test environments +1. no specification = latest opal (1.x) and latest rails (6.x) +2. OPAL_VERSION='~> 0.11' = opal 0.11 and forces rails 5.x +3. RAILS_VERSION='~> 5.0' = rails 5.x and defaults to latest opal (1.0) +--- +### Opal require implementation changed + replace ```javascript @@ -9,14 +35,22 @@ Opal.loaded(OpalLoaded || []); Opal.require("components"); ``` - `require 'opal'` must be executed before any ruby code (including other requires.) This was not true in Opal 0.11 -hyperstack-config seems to be upgraded to use this method but unless there is some trick it will fail if running on Opal 0.11. See hyper-state's test-app app/assets/application.js file for a way to make a switch, but perhaps hyperstack-config does this by redefining Opal.loaded? +hyperstack-config semantically figures out the right method, so if you use hyperstack-config consistently to load the opal code, all will be well! ------------- +Typically: + ++ app/assets/javascript/application.js +should have the line: +`//= require hyperstack-loader` ++ add remove other dependencies using Hyperstack.import + +--- -@@vars: Are now implemented correctly (I guess) so that they are lexically scoped, thus this does not work: +### @@vars properly implemented + +@@vars: Are now implemented correctly (I guess) so that they are lexically scoped, thus this does **not** work: ``` Foo.class_eval do @@ -25,35 +59,64 @@ Foo.class_eval do end end ``` -------------- +because its lexically scoped, the compiler looks for an inclosing class definition for @@foo, not what you want. + +in this case simply replace `Foo.class_eval do` with `class Foo` + +--- +### opal-browser must be taken from master to work with 1.0 + +opal-browser not released for opal 1.0 yet, causing deprecation warnings. To work around this we include browser in the hypercomponent Gemfile (not gemspec) unless the OPAL_VERSION environment variable is set to 0.11 -opal-browser not released for opal 1.0 yet, causing deprecation warnings. hyper-component loads opal-browser from master so that spec ./spec/client_features/component_spec.rb:499 can pass, but we at least need a note on how to get rid of these warnings (get opal-browser from master) until opal-browser is released. +TODO: Release opal-browser! ------------ +### compiler checks for JS undefined, empty objects, and null in more places + This code will no longer work as expected: ```ruby x = `{}` -"foo(#{x})" # <- generates "foo("+x+")" which results in the following string "foo([Object Object])" +"foo(#{x})" # <- generates "foo("+x+")" which +# results in the following string "foo([Object Object])" ``` not sure how this worked before, but it did. ------------- - +```ruby array.each do |e| - # be careful any values e that are `null` will get rewritten as `nil` fine I guess most of the time - # but caused some problems in specs where we are explicitly trying to iterate over JS values + # be careful any values e that are `null` will + # get rewritten as `nil` fine I guess most of the time + # but caused some problems in specs where we are + # explicitly trying to iterate over JS values # see /spec/client_features/component_spec.rb:656 +``` + +------------ + +### back quotes operator now works properly in hyper-spec blocks + +This now works properly: +```ruby + evaluate_ruby do + ... + foo = `JSON.stringify(x)` + ... + end +``` ------------ + +### TODO: inconsistent dependency on server_rendering.js + search around for references to config.react.server_renderer_options and/or "server_rendering.js" inconsistent... should now default to hyperstack_prerender_loader.js and more over there is a hyperstack option as well, so the server_renderer file option should match + ------------ -Opal splat works properly in 1.0! +### Opal splat works properly in 1.0! ```ruby foo(*[1, 2], [[3, 4]]) @@ -68,19 +131,51 @@ is there a fix that works for both 1.0 and 0.9 ? or do we need a SWITCH??? Found this first in hyper-model, so in hyper-model these are going to be marked with # SPLAT BUG with the old code until things get sorted + +------------ + +### Can't inherit directly from ApplicationController::Base in Rails 6.0 ... + ------------ -Can't inherit directly from ApplicationController::Base in Rails 6.0 ... +### TODO pull in next-gen-hyperspec branch + +------------ + +### TODO figure out how to make libv8 dependency optional + +----------- +### Patch selenium webdriver -MEANWHILE REMEMBER - to pull in next-gen-hyperspec branch -AND figure out how to make libv8 dependency optional +Hyper-spec currently depends on an older version of selenium webdriver, and so requires this patch be applied to get logging to work: +```ruby +module Selenium + module WebDriver + module Chrome + module Bridge + COMMANDS = remove_const(:COMMANDS).dup + COMMANDS[:get_log] = [:post, 'session/:session_id/log'] + COMMANDS.freeze + + def log(type) + data = execute :get_log, {}, {type: type.to_s} + + Array(data).map do |l| + begin + LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message') + rescue KeyError + next + end + end + end + end + end + end +end +``` -Progress: +TODO: figure out if we have to depend on this old version -hyper-spec: Still needs to be merged with the next gen branch (note have to include a patch the selenium-webdriver in hyper-spec. Investigate why we are dependent on an old version?) -(the rest of these are passing using current hyper-spec syntax) -hyperstack-config: passing -hyper-state: passing -hyper-component: need to get error logs (see spec_helper line 43) plus plenty of other failures. +--- From a8c896e9bbcc6f1cdfd4c4a877cce382a7456fb7 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 12 Jan 2021 12:28:13 -0500 Subject: [PATCH 036/307] DummyValue class method works again --- ruby/hyper-model/Gemfile | 1 - ruby/hyper-model/hyper-model.gemspec | 15 ++------------- .../active_record/reactive_record/dummy_value.rb | 5 +++++ upgrade-from-opal-0.11-rails-5.md | 15 +++++++++++++++ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ruby/hyper-model/Gemfile b/ruby/hyper-model/Gemfile index 4be049818..a5a0cd35b 100644 --- a/ruby/hyper-model/Gemfile +++ b/ruby/hyper-model/Gemfile @@ -2,7 +2,6 @@ source 'https://rubygems.org' #gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master" # hyper-model is still using an ancient inlined version of hyper-spec #gem 'hyper-spec', path: '../hyper-spec' -gem 'hyperstack-config', path: '../hyperstack-config' gem 'hyper-state', path: '../hyper-state' gem 'hyper-component', path: '../hyper-component' gem 'hyper-operation', path: '../hyper-operation' diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 06f4a3ba6..7329a92e4 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -27,32 +27,22 @@ Gem::Specification.new do |spec| spec.add_dependency 'activemodel' spec.add_dependency 'activerecord', '>= 4.0.0' - spec.add_dependency 'hyper-component', HyperModel::VERSION spec.add_dependency 'hyper-operation', HyperModel::VERSION - spec.add_dependency 'hyperstack-config', HyperModel::VERSION spec.add_development_dependency 'bundler' - # spec.add_development_dependency 'capybara', '~> 3.33.0' - # spec.add_development_dependency 'chromedriver-helper', '1.2.0' - # spec.add_development_dependency 'mini_racer', '~> 0.2.6' - # spec.add_development_dependency 'selenium-webdriver' spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'factory_bot_rails' spec.add_development_dependency 'hyper-spec', HyperModel::VERSION spec.add_development_dependency 'mysql2' - # spec.add_development_dependency 'opal-activesupport', '~> 0.3.1' - # spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' - # spec.add_development_dependency 'parser', '~> 2.3.3.1' - spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-rescue' + spec.add_development_dependency 'pry-stack_explorer' spec.add_development_dependency 'puma' spec.add_development_dependency 'pusher' spec.add_development_dependency 'pusher-fake' - spec.add_development_dependency 'rails', '>= 5.0.0' + spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' - # spec.add_development_dependency 'reactrb-rails-generator' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' spec.add_development_dependency 'rspec-its' @@ -66,5 +56,4 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'spring-commands-rspec', '~> 1.0.4' spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6' spec.add_development_dependency 'timecop', '~> 0.8.1' - #spec.add_development_dependency 'unparser', '~> 0.4.2' end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index 7a274aed3..bf29b0bf0 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -106,6 +106,11 @@ def ! true end + def class + notify + @object.class + end + def method_missing(method, *args, &block) if method.start_with?("build_default_value_for_") nil diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index 06977e662..8a4c373fd 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -179,3 +179,18 @@ end TODO: figure out if we have to depend on this old version --- + +### BasicObject responds_to the class method + +This means that a `DummyValue` returns `DummyValue` instead of the class its wrapping. Simple fix is to add + +```ruby +def class + notify + @object.class +end +``` + +which is what the method missing would do... + +--- From 76fb3addbae69ab69a016fb99debb8f8b2fef990 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 12 Jan 2021 16:31:37 -0500 Subject: [PATCH 037/307] fixed remaining bugs in hyper-model --- .../active_record/instance_methods.rb | 14 ++++++++++++++ .../active_record/reactive_record/dummy_value.rb | 10 +--------- ruby/rails-hyperstack/rails-hyperstack.gemspec | 9 --------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb index 0eccace04..7b3969884 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb @@ -14,7 +14,21 @@ module InstanceMethods # as well as for belongs_to relationships, server_methods, and the special # type and model_name methods. See the ClassMethods module for details. + # meanwhile in Opal 1.0 there is currently an issue where the name of the method + # does not get passed to method_missing from super. + # https://github.com/opal/opal/issues/2165 + # So the following hack works around that issue until its fixed. + + %x{ + Opal.orig_find_super_dispatcher = Opal.find_super_dispatcher + Opal.find_super_dispatcher = function(obj, mid, current_func, defcheck, allow_stubs) { + Opal.__name_of_super = mid; + return Opal.orig_find_super_dispatcher(obj, mid, current_func, defcheck, allow_stubs) + } + } + def method_missing(missing, *args, &block) + missing ||= `Opal.__name_of_super` column = self.class.columns_hash.detect { |name, *| missing =~ /^#{name}/ } if column name = column[0] diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index bf29b0bf0..d025a6e35 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -1,4 +1,4 @@ -# add mehods to Object to determine if this is a dummy object or not +# add methods to Object to determine if this is a dummy object or not class Object def loaded? !loading? @@ -7,10 +7,6 @@ def loaded? def loading? false end - - def present? - !!self - end end module ReactiveRecord @@ -94,10 +90,6 @@ def loaded? false end - def present? - false - end - def nil? true end diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 1bb9e7c43..5eaf26b30 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -64,13 +64,6 @@ You can control how much of the stack gets installed as well: spec.add_dependency 'mini_racer', '~> 0.2.6' spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' - spec.add_dependency 'opal', ENV['OPAL_VERSION'] if ENV['OPAL_VERSION'] - - - # spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153 - # #spec.add_development_dependency 'chromedriver-helper' - # spec.add_development_dependency 'geminabox', '>= 0.13.11' - spec.add_development_dependency 'bundler' spec.add_development_dependency 'chromedriver-helper' @@ -78,7 +71,6 @@ You can control how much of the stack gets installed as well: spec.add_development_dependency 'pry' spec.add_development_dependency 'puma' spec.add_development_dependency 'bootsnap' - #spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4' # was 1.3.6 -- see https://github.com/rails/rails/issues/35153 @@ -96,5 +88,4 @@ You can control how much of the stack gets installed as well: spec.add_development_dependency 'jbuilder', '~> 2.5' spec.add_development_dependency 'foreman' spec.add_development_dependency 'database_cleaner' - end From d3cd2ce310bad48c82e0d8fc0d9ba647385b8178 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 12 Jan 2021 19:45:14 -0500 Subject: [PATCH 038/307] wip last couple of problems in hypermodel --- .../spec/batch5/authorization_spec.rb | 12 ++++++------ ...st_be_last_relationship_permissions_spec.rb | 2 ++ .../spec/support/component_helpers.rb | 4 ++-- .../lib/hyper-spec/component_test_helpers.rb | 2 ++ upgrade-from-opal-0.11-rails-5.md | 18 ++++++++++++++++++ 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/ruby/hyper-model/spec/batch5/authorization_spec.rb b/ruby/hyper-model/spec/batch5/authorization_spec.rb index e3da7e7de..ba84b376c 100644 --- a/ruby/hyper-model/spec/batch5/authorization_spec.rb +++ b/ruby/hyper-model/spec/batch5/authorization_spec.rb @@ -82,7 +82,7 @@ def log(*args) end wait_for_ajax ApplicationController.acting_user = User.new(name: 'fred') - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') evaluate_ruby do TestModel.all[0].test_attribute end @@ -100,7 +100,7 @@ def create_permitted? mount 'TestComponent2' wait_for_ajax ApplicationController.acting_user = User.new(name: 'fred') - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') TestModel.before_save { self.test_attribute ||= 'top secret' } expect_promise do model = TestModel.new(updated_at: 12) @@ -117,7 +117,7 @@ def create_permitted? model1.attributes_on_client(page).should eq({id: 1}) wait_for_ajax ApplicationController.acting_user = User.new(name: "fred") - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') wait_for_ajax # sleep a little, to make sure that on fast systems the seconds precision is covered sleep 2 @@ -132,7 +132,7 @@ def create_permitted? m1_attr_cl1[:created_at].to_time.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z').should eq(model1.created_at.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z')) m1_attr_cl1[:updated_at].to_time.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z').should eq(model1.updated_at.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z')) ApplicationController.acting_user = User.new(name: "george") - page.evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") + evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") wait_for_ajax sleep 2 model1.update_attribute(:completed, true) @@ -149,7 +149,7 @@ def create_permitted? client_option raise_on_js_errors: :off mount "TestComponent2" model1 = FactoryBot.create(:test_model, test_attribute: "hello") - page.evaluate_ruby('Hyperstack.connect("TestApplication")') + evaluate_ruby('Hyperstack.connect("TestApplication")') model1.update_attribute(:test_attribute, 'george') wait_for_ajax model1.attributes_on_client(page).should eq({id: 1}) @@ -161,7 +161,7 @@ def create_permitted? mount "TestComponent2" model1 = FactoryBot.create(:test_model, test_attribute: "george") ApplicationController.acting_user = User.new(name: "fred") - page.evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") + evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") model1.update_attribute(:completed, true) wait_for_ajax model1.attributes_on_client(page).should eq({id: 1}) diff --git a/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb b/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb index 62788821f..cb9a4432c 100644 --- a/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb +++ b/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb @@ -278,6 +278,7 @@ end it 'will allow access via scopes' do + Hyperstack::Connection.show_diagnostics = true isomorphic do TodoItem.scope :annuder_scope, ->() { all } TodoItem.scope :test_scope, ->() { all }, regulate: :always_allow @@ -290,6 +291,7 @@ class TestComponentZ < HyperComponent end end end + binding.pry expect(page).to have_content("There is 1 TodoItem") TodoItem.create expect(page).to have_content("There is 2 TodoItem") diff --git a/ruby/hyper-model/spec/support/component_helpers.rb b/ruby/hyper-model/spec/support/component_helpers.rb index e9b1c935e..13c10ac54 100644 --- a/ruby/hyper-model/spec/support/component_helpers.rb +++ b/ruby/hyper-model/spec/support/component_helpers.rb @@ -471,7 +471,7 @@ def open_in_chrome def pause(message = nil) if message puts message - page.evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" + evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" end page.evaluate_script("window.hyper_spec_waiting_for_go = true") loop do @@ -522,7 +522,7 @@ def check_errors config.before(:all) do ActiveRecord::Base.class_eval do def attributes_on_client(page) - page.evaluate_ruby("#{self.class.name}.find(#{id}).attributes", symbolize_names: true) + evaluate_ruby("#{self.class.name}.find(#{id}).attributes", symbolize_names: true) end end end diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 42451259f..01a7ab369 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -350,6 +350,8 @@ def size_window(width = nil, height = nil) Capybara.current_session.current_window .resize_to(width + RSpec.configuration.debugger_width, height) wait_for_size(width + RSpec.configuration.debugger_width, height) + rescue StandardError + true end end diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index 8a4c373fd..89f925a7b 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -194,3 +194,21 @@ end which is what the method missing would do... --- + +### Opal 1.0 has trouble with super and method_missing + +If super is called within a method that is not defined in the super class, the superclasses' method missing +is called, but the method name is not provided. + +This has been worked around, and a bug has been filed. + +--- + +### Object.present? should not have been defined + +The DummyValue system was defining present? in both DummyValue and Object. This should not be necessary as +present? is defined by activesupport. In 0.11 it didnt' matter because the load order defined activesupport's +version AFTER the bogus one in the dummy value system. In 1.0 the order for some reason is reversed, so +the correct version in activesupport was being overwritten. + +I don't believe this will have any effect on application code. From 0b42d32dea9c910007f95d840c20996f6c53c094 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 12 Jan 2021 22:16:16 -0500 Subject: [PATCH 039/307] fixes to hyper-spec and spec issues in hyper-model --- ...t_be_last_relationship_permissions_spec.rb | 2 -- .../lib/hyper-spec/component_test_helpers.rb | 18 +++++++++++- upgrade-from-opal-0.11-rails-5.md | 28 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb b/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb index cb9a4432c..62788821f 100644 --- a/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb +++ b/ruby/hyper-model/spec/batch5/zzz_must_be_last_relationship_permissions_spec.rb @@ -278,7 +278,6 @@ end it 'will allow access via scopes' do - Hyperstack::Connection.show_diagnostics = true isomorphic do TodoItem.scope :annuder_scope, ->() { all } TodoItem.scope :test_scope, ->() { all }, regulate: :always_allow @@ -291,7 +290,6 @@ class TestComponentZ < HyperComponent end end end - binding.pry expect(page).to have_content("There is 1 TodoItem") TodoItem.create expect(page).to have_content("There is 2 TodoItem") diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 01a7ab369..47bb3f64f 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -28,10 +28,26 @@ def display_example_description end end + # TODO: verify this works in all cases... + # at one time it was ApplicationController + # then it changed to ::ActionController::Base (going to rails 5?) + # but HyperModel has specs that depend on it being ApplicationController + # We could use the controller param in those cases, but it seems reasonable + # that by default we would use ApplicationController if it exists and fallback + # to ActionController::Base + + def new_controller + if defined? ApplicationController + Class.new ApplicationController + else + Class.new ::ActionController::Base + end + end + def build_test_url_for(controller) unless controller unless defined?(::HyperstackTestController) - Object.const_set('HyperstackTestController', Class.new(::ActionController::Base)) + Object.const_set('HyperstackTestController', new_controller) end controller = ::HyperstackTestController diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index 89f925a7b..3fce299de 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -212,3 +212,31 @@ version AFTER the bogus one in the dummy value system. In 1.0 the order for som the correct version in activesupport was being overwritten. I don't believe this will have any effect on application code. + +--- + +### HyperSpec is not using ApplicationController + +HyperModel had its own version of HyperSpec. In that version the test controller was subclassed from ApplicationController. In the new version its subclassed from ActionController::Base. + +In some HyperModel specs it was setting up mocks in ApplicationController, and these were not being used. + +so now hyper-spec tries to use ApplicationController and falls back to ::ActionController::Base. + +TODO: verify this works in all cases... + +--- + +### Trying to set window size outside of a JS spec no longer works + +Who knows why, but for now we just rescue any failure, and keep going. + +--- + +### page.evaluate_ruby no longer works but plain evaluate_ruby does. + +Only one spec files was doing that, so just upgraded. + +TODO: get page.evaluate_ruby working again. + +--- From c91d41b8b8e140f4f4d8ee438dd869c9de3b7cfd Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 05:39:37 -0500 Subject: [PATCH 040/307] first attempt at matrix run --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c1df777a..854fbcf64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,7 +52,10 @@ _deploy_gem: &_deploy_gem secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" on: tags: true - +env: + - OPAL_VERSION='~> 0.11.4' + - RAILS_VERSION='~> 5.0' + - LASTEST_VERSIONS=true jobs: include: - <<: *_test_gem From 27cb1d30d7b2235ec5ce8d9a86aedf43a8ae0a30 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 06:43:41 -0500 Subject: [PATCH 041/307] fix in client_drivers and more spec_helper fixes --- .../spec/support/component_helpers.rb | 529 ------------------ .../transport/client_drivers.rb | 2 +- ruby/hyper-spec/lib/hyper-spec.rb | 10 +- .../lib/hyper-spec/component_test_helpers.rb | 6 +- 4 files changed, 11 insertions(+), 536 deletions(-) delete mode 100644 ruby/hyper-model/spec/support/component_helpers.rb diff --git a/ruby/hyper-model/spec/support/component_helpers.rb b/ruby/hyper-model/spec/support/component_helpers.rb deleted file mode 100644 index 13c10ac54..000000000 --- a/ruby/hyper-model/spec/support/component_helpers.rb +++ /dev/null @@ -1,529 +0,0 @@ -# see component_test_helpers_spec.rb for examples - -require 'parser/current' -require 'unparser' - -Parser::Builders::Default.emit_procarg0 = true - -#require 'pry' - -module ComponentTestHelpers - - def self.compile_to_opal(&block) - Opal.compile(block.source.split("\n")[1..-2].join("\n")) - end - - - TOP_LEVEL_COMPONENT_PATCH = lambda { |&block| Opal.compile(block.source.split("\n")[1..-2].join("\n"))}.call do #ComponentTestHelpers.compile_to_opal do - module Hyperstack - module Internal - module Component - class TopLevelRailsComponent - - # original class declares these params: - # param :component_name - # param :controller - # param :render_params - - class << self - attr_accessor :event_history - - def callback_history_for(proc_name) - event_history[proc_name] - end - - def last_callback_for(proc_name) - event_history[proc_name].last - end - - def clear_callback_history_for(proc_name) - event_history[proc_name] = [] - end - - def event_history_for(event_name) - event_history["on_#{event_name}"] - end - - def last_event_for(event_name) - event_history["on_#{event_name}"].last - end - - def clear_event_history_for(event_name) - event_history["on_#{event_name}"] = [] - end - end - - def component - return @component if @component - paths_searched = [] - component = nil - if @ComponentName.start_with?('::') - # if absolute path of component is given, look it up and fail if not found - paths_searched << @ComponentName - component = begin - Object.const_get(@ComponentName) - rescue NameError - nil - end - else - # if relative path is given, look it up like this - # 1) we check each path + controller-name + component-name - # 2) if we can't find it there we check each path + component-name - # if we can't find it we just try const_get - # so (assuming controller name is Home) - # ::Foo::Bar will only resolve to some component named ::Foo::Bar - # but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar - self.class.search_path.each do |scope| - paths_searched << "#{scope.name}::#{@Controller}::#{@ComponentName}" - component = begin - scope.const_get(@Controller, false).const_get(@ComponentName, false) - rescue NameError - nil - end - break if component != nil - end - unless component - self.class.search_path.each do |scope| - paths_searched << "#{scope.name}::#{@ComponentName}" - component = begin - scope.const_get(@ComponentName, false) - rescue NameError - nil - end - break if component != nil - end - end - end - @component = component - return @component if @component && @component.method_defined?(:render) - raise "Could not find component class '#{@ComponentName}' for @Controller '#{@Controller}' in any component directory. Tried [#{paths_searched.join(", ")}]" - end - - before_mount do - TopLevelRailsComponent.event_history = Hash.new { |h, k| h[k] = [] } - component.validator.rules.each do |name, rules| - next unless rules[:type] == Proc - - TopLevelRailsComponent.event_history[name] = [] - @RenderParams[name] = lambda do |*args| - TopLevelRailsComponent.event_history[name] << args - end - end - end - - def render - Hyperstack::Internal::Component::RenderingContext.render(component, @RenderParams) - end - end - end - end - end - - # module React - # class TopLevelRailsComponent # NEEDS TO BE Hyperstack::Internal::Component::TopLevelRailsComponent - # - # class << self - # attr_accessor :event_history - # - # def callback_history_for(proc_name) - # event_history[proc_name] - # end - # - # def last_callback_for(proc_name) - # event_history[proc_name].last - # end - # - # def clear_callback_history_for(proc_name) - # event_history[proc_name] = [] - # end - # - # def event_history_for(event_name) - # event_history["_on#{event_name.event_camelize}"] - # end - # - # def last_event_for(event_name) - # event_history["_on#{event_name.event_camelize}"].last - # end - # - # def clear_event_history_for(event_name) - # event_history["_on#{event_name.event_camelize}"] = [] - # end - # - # end - # - # def component - # return @component if @component - # paths_searched = [] - # component = nil - # if params.component_name.start_with?('::') - # # if absolute path of component is given, look it up and fail if not found - # paths_searched << params.component_name - # component = begin - # Object.const_get(params.component_name) - # rescue NameError - # nil - # end - # else - # # if relative path is given, look it up like this - # # 1) we check each path + controller-name + component-name - # # 2) if we can't find it there we check each path + component-name - # # if we can't find it we just try const_get - # # so (assuming controller name is Home) - # # ::Foo::Bar will only resolve to some component named ::Foo::Bar - # # but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar - # self.class.search_path.each do |scope| - # paths_searched << "#{scope.name}::#{params.controller}::#{params.component_name}" - # component = begin - # scope.const_get(params.controller, false).const_get(params.component_name, false) - # rescue NameError - # nil - # end - # break if component != nil - # end - # unless component - # self.class.search_path.each do |scope| - # paths_searched << "#{scope.name}::#{params.component_name}" - # component = begin - # scope.const_get(params.component_name, false) - # rescue NameError - # nil - # end - # break if component != nil - # end - # end - # end - # @component = component - # return @component if @component && @component.method_defined?(:render) - # raise "Could not find component class '#{params.component_name}' for params.controller '#{params.controller}' in any component directory. Tried [#{paths_searched.join(", ")}]" - # end - # - # before_mount do - # # NEEDS TO BE Hyperstack::Internal::Component::TopLevelRailsComponent - # TopLevelRailsComponent.event_history = Hash.new {|h,k| h[k] = [] } - # component.validator.rules.each do |name, rules| - # if rules[:type] == Proc - # # NEEDS TO BE Hyperstack::Internal::Component::TopLevelRailsComponent - # TopLevelRailsComponent.event_history[name] = [] - # params.render_params[name] = lambda { |*args| TopLevelRailsComponent.event_history[name] << args.collect { |arg| Native(arg).to_n } } - # end - # end - # end - # - # def render - # Hyperstack::Internal::Component::RenderingContext.render(component, params.render_params) - # end - # end - # end - end - - def build_test_url_for(controller) - - unless controller - Object.const_set("ReactTestController", Class.new(ApplicationController)) unless defined?(::ReactTestController) - controller = ::ReactTestController - end - - route_root = controller.name.gsub(/Controller$/,"").underscore - - unless controller.method_defined? :test - controller.class_eval do - define_method(:test) do - route_root = self.class.name.gsub(/Controller$/,"").underscore - test_params = Rails.cache.read("/#{route_root}/#{params[:id]}") - @component_name = test_params[0] - @component_params = test_params[1] - render_params = test_params[2] - render_on = render_params.delete(:render_on) || :client_only - mock_time = render_params.delete(:mock_time) - style_sheet = render_params.delete(:style_sheet) - javascript = render_params.delete(:javascript) - code = render_params.delete(:code) - page = "<%= react_component @component_name, @component_params, { prerender: #{render_on != :client_only} } %>" # false should be: "#{render_on != :client_only} } %>" but its not working in the gem testing harness - unless render_on == :server_only - page = "\n#{page}" - page = "\n"+page if code - end - - #TODO figure out how to auto insert this line???? something like: - #page = "<%= javascript_include_tag 'reactrb-router' %>\n#{page}" - - if (render_on != :server_only && !render_params[:layout]) || javascript - #page = "\n"+page - page = "<%= javascript_include_tag '#{javascript || 'application'}' %>\n"+page - end - if mock_time || (defined?(Timecop) && Timecop.top_stack_item) - puts "********** WARNING LOLEX NOT AVAILABLE TIME ON CLIENT WILL NOT MATCH SERVER **********" - # unix_millis = ((mock_time || Time.now).to_f * 1000.0).to_i - # page = "<%= javascript_include_tag 'spec/libs/lolex' %>\n"+ - # "\n"+page - end - if !render_params[:layout] || style_sheet - page = "<%= stylesheet_link_tag '#{style_sheet || 'application'}' rescue nil %>\n"+page - end - page = "\n#{page}" - title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) - title = "#{title}...continued." if ComponentTestHelpers.description_displayed - page = "\n#{page}" - ComponentTestHelpers.description_displayed = true - render_params[:inline] = page - render render_params - end - end - - # test_routes = Proc.new do - # get "/#{route_root}/:id", to: "#{route_root}#test" - # end - # Rails.application.routes.eval_block(test_routes) - - begin - routes = Rails.application.routes - routes.disable_clear_and_finalize = true - routes.clear! - routes.draw do - get "/#{route_root}/:id", to: "#{route_root}#test" - end - Rails.application.routes_reloader.paths.each{ |path| load(path) } - routes.finalize! - ActiveSupport.on_load(:action_controller) { routes.finalize! } - ensure - routes.disable_clear_and_finalize = false - end - end - - "/#{route_root}/#{@test_id = (@test_id || 0) + 1}" - - end - - def isomorphic(&block) - yield - on_client(&block) - end - - def evaluate_ruby(str="", opts={}, &block) - insure_mount - str = "#{str}\n#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}" if block - js = Opal.compile(str).gsub("// Prepare super implicit arguments\n", "").gsub("\n","").gsub("(Opal);","(Opal)") - JSON.parse(evaluate_script("[#{js}].$to_json()"), opts).first - end - - def expect_evaluate_ruby(str = '', opts = {}, &block) - expect(evaluate_ruby(add_opal_block(str, block), opts)) - end - - def add_opal_block(str, block) - # big assumption here is that we are going to follow this with a .to - # hence .children.first followed by .children.last - # probably should do some kind of "search" to make this work nicely - return str unless block - "#{str}\n"\ - "#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.first.children.last}" - end - - def evaluate_promise(str = '', opts = {}, &block) - insure_mount - str = "#{str}\n#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}" if block - str = "#{str}.then { |args| args = [args]; `window.hyper_spec_promise_result = args` }" - js = Opal.compile(str).gsub("\n","").gsub("(Opal);","(Opal)") - page.evaluate_script("window.hyper_spec_promise_result = false") - page.execute_script(js) - Timeout.timeout(100) do #Capybara.default_max_wait_time) do - loop do - sleep 0.25 - break if page.evaluate_script("!!window.hyper_spec_promise_result") - end - end - JSON.parse(page.evaluate_script("window.hyper_spec_promise_result.$to_json()"), opts).first - end - - def expect_promise(str = '', opts = {}, &block) - insure_mount - expect(evaluate_promise(add_opal_block(str, block), opts)) - end - - def ppr(str) - js = Opal.compile(str).gsub("\n","").gsub("(Opal);","(Opal)") - execute_script("console.log(#{js})") - end - - - def on_client(&block) - @client_code = "#{@client_code}#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}\n" - end - - def debugger - `debugger` - nil - end - - class << self - attr_accessor :current_example - attr_accessor :description_displayed - def display_example_description - "" - end - end - - def insure_mount - # rescue in case page is not defined... - mount unless page.instance_variable_get("@hyper_spec_mounted") rescue nil - end - - def client_option(opts = {}) - @client_options ||= {} - @client_options.merge! opts - end - - alias client_options client_option - - def mount(component_name = nil, params = nil, opts = {}, &block) - unless params - params = opts - opts = {} - end - test_url = build_test_url_for(opts.delete(:controller)) - if block || @client_code || component_name.nil? - block_with_helpers = <<-code - module ComponentHelpers - def self.js_eval(s) - `eval(s)` - end - def self.dasherize(s) - %x{ - return s.replace(/[-_\\s]+/g, '-') - .replace(/([A-Z\\d]+)([A-Z][a-z])/g, '$1-$2') - .replace(/([a-z\\d])([A-Z])/g, '$1-$2') - .toLowerCase() - } - end - def self.add_class(class_name, styles={}) - style = styles.collect { |attr, value| "\#{dasherize(attr)}:\#{value}"}.join("; ") - cs = class_name.to_s - %x{ - var style_el = document.createElement("style"); - var css = "." + cs + " { " + style + " }"; - style_el.type = "text/css"; - if (style_el.styleSheet){ - style_el.styleSheet.cssText = css; - } else { - style_el.appendChild(document.createTextNode(css)); - } - document.head.appendChild(style_el); - } - end - end - class HyperComponent::HyperTestDummy < HyperComponent - render {} - end - #{@client_code} - #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} - code - opts[:code] = Opal.compile(block_with_helpers) - end - component_name ||= 'HyperComponent::HyperTestDummy' - ::Rails.cache.write(test_url, [component_name, params, opts]) - - # this code copied from latest hyper-spec - test_code_key = "hyper_spec_prerender_test_code.js" - #::Rails.configuration.react.server_renderer_options[:files] ||= ['hyperstack-prerender-loader.js'] - @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] || [] #'hyperstack-prerender-loader.js'] - if opts[:render_on] == :both || opts[:render_on] == :server_only - unless opts[:code].blank? - ::Rails.cache.write(test_code_key, opts[:code]) - ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + [test_code_key] - ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes - else - ::Rails.cache.delete(test_code_key) - ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files - ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes - end - end - # end of copied code - - visit test_url - wait_for_ajax unless opts[:no_wait] - page.instance_variable_set("@hyper_spec_mounted", true) - end - - [:callback_history_for, :last_callback_for, :clear_callback_history_for, :event_history_for, :last_event_for, :clear_event_history_for].each do |method| - define_method(method) { |event_name| evaluate_script("Opal.React.TopLevelRailsComponent.$#{method}('#{event_name}')") } - end - - def run_on_client(&block) - script = Opal.compile(Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last) - execute_script(script) - end - - def open_in_chrome - if false && ['linux', 'freebsd'].include?(`uname`.downcase) - `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}` - else - `open http://#{page.server.host}:#{page.server.port}#{page.current_path}` - end - while true - sleep 1.hour - end - end - - def pause(message = nil) - if message - puts message - evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" - end - page.evaluate_script("window.hyper_spec_waiting_for_go = true") - loop do - sleep 0.25 - break unless page.evaluate_script("window.hyper_spec_waiting_for_go") - end - end - - def size_window(width=nil, height=nil) - width, height = width if width.is_a? Array - portrait = true if height == :portrait - case width - when :small - width, height = [480, 320] - when :mobile - width, height = [640, 480] - when :tablet - width, height = [960, 640] - when :large - width, height = [1920, 6000] - when :default, nil - width, height = [1024, 768] - end - if portrait - width, height = [height, width] - end - if page.driver.browser.respond_to?(:manage) - page.driver.browser.manage.window.resize_to(width, height) - elsif page.driver.respond_to?(:resize) - page.driver.resize(width, height) - end - end - - def check_errors - logs = page.driver.browser.manage.logs.get(:browser) - errors = logs.select { |e| e.level == "SEVERE" && e.message.present? } - .map { |m| m.message.gsub(/\\n/, "\n") }.to_a - puts "WARNING - FOUND UNEXPECTED ERRORS #{errors}" if errors.present? - end - -end - -RSpec.configure do |config| - config.before(:each) do |example| - ComponentTestHelpers.current_example = example - ComponentTestHelpers.description_displayed = false - end - config.before(:all) do - ActiveRecord::Base.class_eval do - def attributes_on_client(page) - evaluate_ruby("#{self.class.name}.find(#{id}).attributes", symbolize_names: true) - end - end - end -end diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb b/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb index de325c48b..4f93c4ba8 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb @@ -169,7 +169,7 @@ def self.sync_dispatch(data) config_hash = { transport: Hyperstack.transport, id: id, - acting_user_id: (controller.acting_user && controller.acting_user.id), + acting_user_id: (controller.acting_user.respond_to?(:id) && controller.acting_user.id), env: ::Rails.env, client_logging: Hyperstack.client_logging, pusher_fake_js: pusher_fake_js, diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index b4d56e9cc..14c7ac641 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -8,9 +8,13 @@ require 'selenium/web_driver/firefox/profile' RSpec.configure do |config| - config.include HyperSpec::ComponentTestHelpers - config.include HyperSpec::WaitForAjax - config.include Capybara::DSL + + # these three were config.include, but that did not + # add the methods to the page object + # so you couldn't say page.evaluate_ruby for example + include HyperSpec::ComponentTestHelpers + include HyperSpec::WaitForAjax + include Capybara::DSL config.mock_with :rspec diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 47bb3f64f..31db813f0 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -12,6 +12,7 @@ module HyperSpec module ComponentTestHelpers + TOP_LEVEL_COMPONENT_PATCH = Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) TIME_COP_CLIENT_PATCH = @@ -376,9 +377,8 @@ def size_window(width = nil, height = nil) ComponentTestHelpers.current_example = example ComponentTestHelpers.description_displayed = false end - - if defined?(ActiveRecord) - config.before(:all) do + config.before(:all) do + if defined?(ActiveRecord) ActiveRecord::Base.class_eval do def attributes_on_client(page) page.evaluate_ruby("#{self.class.name}.find(#{id}).attributes", symbolize_names: true) From 772767056319eb0c42902635aeb439be1e915a24 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 08:09:21 -0500 Subject: [PATCH 042/307] one more time with this component test helpers problems --- .travis.yml | 9 ++++---- .../spec/batch5/authorization_spec.rb | 10 ++++---- ruby/hyper-spec/lib/hyper-spec.rb | 9 +++----- .../lib/hyper-spec/component_test_helpers.rb | 13 ++++------- upgrade-from-opal-0.11-rails-5.md | 23 +++++++++++++++++-- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 854fbcf64..2f75516c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,14 +52,13 @@ _deploy_gem: &_deploy_gem secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" on: tags: true -env: - - OPAL_VERSION='~> 0.11.4' - - RAILS_VERSION='~> 5.0' - - LASTEST_VERSIONS=true jobs: include: - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + env: + - COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + - COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~> 0.11.4' + - COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~> 5.0' - <<: *_test_gem env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - <<: *_test_gem diff --git a/ruby/hyper-model/spec/batch5/authorization_spec.rb b/ruby/hyper-model/spec/batch5/authorization_spec.rb index ba84b376c..c4d9ea831 100644 --- a/ruby/hyper-model/spec/batch5/authorization_spec.rb +++ b/ruby/hyper-model/spec/batch5/authorization_spec.rb @@ -114,7 +114,7 @@ def create_permitted? mount "TestComponent2" model1 = FactoryBot.create(:test_model, test_attribute: "hello") wait_for_ajax - model1.attributes_on_client(page).should eq({id: 1}) + attributes_on_client(model1).should eq({id: 1}) wait_for_ajax ApplicationController.acting_user = User.new(name: "fred") evaluate_ruby('Hyperstack.connect("TestApplication")') @@ -127,7 +127,7 @@ def create_permitted? # make sure time zone doesn't matter, as it is about time in space # we get only seconds precision, millisecs are dropped in AR adapters here, but they are in the db with pg # compare only with seconds precision - m1_attr_cl1 = model1.attributes_on_client(page) + m1_attr_cl1 = attributes_on_client(model1) m1_attr_cl1[:id].should eq(1) m1_attr_cl1[:created_at].to_time.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z').should eq(model1.created_at.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z')) m1_attr_cl1[:updated_at].to_time.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z').should eq(model1.updated_at.localtime(0).strftime('%Y-%m-%dT%H:%M:%S%z')) @@ -137,7 +137,7 @@ def create_permitted? sleep 2 model1.update_attribute(:completed, true) wait_for_ajax - m1_attr_cl2 = model1.attributes_on_client(page) + m1_attr_cl2 = attributes_on_client(model1) m1_attr_cl2[:id].should eq(1) m1_attr_cl2[:test_attribute].should eq("george") m1_attr_cl2[:completed].should eq(true) @@ -152,7 +152,7 @@ def create_permitted? evaluate_ruby('Hyperstack.connect("TestApplication")') model1.update_attribute(:test_attribute, 'george') wait_for_ajax - model1.attributes_on_client(page).should eq({id: 1}) + attributes_on_client(model1).should eq({id: 1}) expect_promise('Hyperstack::Model.load { TestModel.find_by_test_attribute("hello") }').to be_nil end @@ -164,7 +164,7 @@ def create_permitted? evaluate_ruby("Hyperstack.connect(['TestModel', #{model1.id}])") model1.update_attribute(:completed, true) wait_for_ajax - model1.attributes_on_client(page).should eq({id: 1}) + attributes_on_client(model1).should eq({id: 1}) end end diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 14c7ac641..c529ccf46 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -9,12 +9,9 @@ RSpec.configure do |config| - # these three were config.include, but that did not - # add the methods to the page object - # so you couldn't say page.evaluate_ruby for example - include HyperSpec::ComponentTestHelpers - include HyperSpec::WaitForAjax - include Capybara::DSL + config.include HyperSpec::ComponentTestHelpers + config.include HyperSpec::WaitForAjax + config.include Capybara::DSL config.mock_with :rspec diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 31db813f0..f29d5fcef 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -291,6 +291,10 @@ def add_class(class_name, style) @client_code = "#{@client_code}ComponentHelpers.add_class('#{class_name}', #{style})\n" end + def attributes_on_client(model) + evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes", symbolize_names: true) + end + def open_in_chrome if false && ['linux', 'freebsd'].include?(`uname`.downcase) `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}` @@ -377,14 +381,5 @@ def size_window(width = nil, height = nil) ComponentTestHelpers.current_example = example ComponentTestHelpers.description_displayed = false end - config.before(:all) do - if defined?(ActiveRecord) - ActiveRecord::Base.class_eval do - def attributes_on_client(page) - page.evaluate_ruby("#{self.class.name}.find(#{id}).attributes", symbolize_names: true) - end - end - end - end end end diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index 3fce299de..c0e008124 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -227,6 +227,18 @@ TODO: verify this works in all cases... --- +### HyperSpec ApplicationController fix breaks client_driver.rb + +A couple of specs were setting `ApplicationController.acting_user = true`. But HyperSpec was not +using the ApplicationController when mounting (see above problem fix) so acting_user remained nil +as far as the client code was concerned. + +Once the above fix was made however client_driver was attempting to do a `controller.acting_user.id` +where acting_user was true, causing a method missing. The code in client_driver was checking for +acting_user being nil, but is now changed to check for `acting_user.respond_to? :id` + +--- + ### Trying to set window size outside of a JS spec no longer works Who knows why, but for now we just rescue any failure, and keep going. @@ -235,8 +247,15 @@ Who knows why, but for now we just rescue any failure, and keep going. ### page.evaluate_ruby no longer works but plain evaluate_ruby does. -Only one spec files was doing that, so just upgraded. +Only one spec files was doing that, so just upgraded. +You can sort of fix it by doing an include instead of config.include in the hyper_spec.rb file +but this causes other problems, including a warning from rspec to not do it. + +Probably will require a general cleanup of application code changing page.evaluate_ruby to just +evaluate_ruby. -TODO: get page.evaluate_ruby working again. +Related to this there was a method called attributes_on_client that was being added to +ActiveRecord::Base. But in order for it to work the "page" was being passed so that you could do +a page.evaluate_ruby, but the whole method was backwards. It should be a capybara helper method that takes a active record model. This is fixed. --- From e678387552fa9fd8c7c790e2f71e688a8dedd972 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 11:57:40 -0500 Subject: [PATCH 043/307] rails-hyperstack good to go --- .travis.yml | 25 ++++++++-------- ruby/rails-hyperstack/Rakefile | 9 ++++-- .../hyperstack/install_generator.rb | 29 ++++++++++--------- .../hyperstack/install_generator_base.rb | 10 +++---- .../install/hyperstack_generator_base.rb | 8 ++--- ruby/rails-hyperstack/spec/gems.rb | 3 +- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f75516c4..fdcbe1270 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,34 +55,35 @@ _deploy_gem: &_deploy_gem jobs: include: - <<: *_test_gem - env: - - COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - - COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~> 0.11.4' - - COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~> 5.0' + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - <<: *_deploy_gem env: COMPONENT=hyper-i18n diff --git a/ruby/rails-hyperstack/Rakefile b/ruby/rails-hyperstack/Rakefile index 7677d765d..19f4b49c3 100644 --- a/ruby/rails-hyperstack/Rakefile +++ b/ruby/rails-hyperstack/Rakefile @@ -1,20 +1,23 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" +require 'pry' RSpec::Core::RakeTask.new(:spec) namespace :spec do task :prepare do + rails_version = `bundle info rails`.match(/\* rails \((.+)\)/)[1] + opal_version = `bundle info opal`.match(/\* opal \((.+)\)/)[1] Dir.chdir('spec') do sh('rm -rf test_app') Bundler.with_clean_env do - sh('rails -v') - sh('rails _6.0.2.1_ new test_app -T') + sh("rails _#{rails_version}_ new test_app -T") end Bundler.with_clean_env do Dir.chdir('test_app') do sh('cat ../gems.rb >> Gemfile') - sh('bundle update') + sh("echo 'gem \"opal\", \"#{opal_version}\"' >> Gemfile") + sh("bundle update") sh('spring stop') sh('bundle exec rails g hyperstack:install') sh('bundle exec rails generate model Sample name:string description:text') diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb index 769b726e1..c6e1c7529 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb @@ -9,12 +9,12 @@ class InstallGenerator < Rails::Generators::Base class_option 'webpack-only', type: :boolean class_option 'hyper-model-only', type: :boolean - def add_clexer - gem 'c_lexer' - Bundler.with_clean_env do - run 'bundle update' - end - end + # def add_clexer + # gem 'c_lexer' + # Bundler.with_clean_env do + # run 'bundle update' + # end + # end def add_component if skip_adding_component? @@ -115,12 +115,13 @@ def install_webpacker Bundler.with_clean_env do run 'bundle install' end - run 'bundle exec rails webpacker:install' + `spring stop` + Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails webpacker:install' } end def create_policies_directory return if skip_hyper_model? - policy_file = File.join('app', 'policies', 'hyperstack', 'application_policy.rb') + policy_file = Rails.root.join('app', 'policies', 'hyperstack', 'application_policy.rb') unless File.exist? policy_file create_file policy_file, <<-RUBY # #{policy_file} @@ -148,10 +149,10 @@ class ApplicationPolicy def move_and_update_application_record return if skip_hyper_model? - rails_app_record_file = File.join('app', 'models', 'application_record.rb') - hyper_app_record_file = File.join('app', 'hyperstack', 'models', 'application_record.rb') + rails_app_record_file = Rails.root.join('app', 'models', 'application_record.rb') + hyper_app_record_file = Rails.root.join('app', 'hyperstack', 'models', 'application_record.rb') unless File.exist? hyper_app_record_file - empty_directory File.join('app', 'hyperstack', 'models') + empty_directory Rails.root.join('app', 'hyperstack', 'models') `mv #{rails_app_record_file} #{hyper_app_record_file}` create_file rails_app_record_file, <<-RUBY # #{rails_app_record_file} @@ -184,7 +185,7 @@ def report say '👩‍✈️ Basic development policy defined. See app/policies/application_policy.rb 👨🏽‍✈️', :green say '💽 HyperModel installed. Move any Active Record models to the app/hyperstack/models to access them from the client 📀', :green end - if File.exist?(init = File.join('config', 'initializers', 'hyperstack.rb')) + if File.exist?(init = Rails.root.join('config', 'initializers', 'hyperstack.rb')) say "☑️ Check #{init} for other configuration options. ☑️", :green end unless skip_hotloader? @@ -217,7 +218,7 @@ def skip_hyper_model? def new_rails_app? # check to see if there are any routes set up and remember it, cause we might add a route in the process @new_rails_app ||= begin - route_file = File.join('config', 'routes.rb') + route_file = Rails.root.join('config', 'routes.rb') count = File.foreach(route_file).inject(0) do |c, line| line = line.strip next c if line.empty? @@ -230,7 +231,7 @@ def new_rails_app? end def inject_into_initializer(s) - file_name = File.join('config', 'initializers', 'hyperstack.rb') + file_name = Rails.root.join('config', 'initializers', 'hyperstack.rb') if File.exist?(file_name) prepend_to_file(file_name) { "#{s}\n" } else diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb index cd9f7eeb9..6cdf9564e 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb @@ -21,7 +21,7 @@ def create_component_file(template) @file_name = component_array.last @indent = 0 template template, - File.join('app', 'hyperstack', 'components', + Rails.root.join('app', 'hyperstack', 'components', *@modules.map(&:downcase), "#{@file_name.underscore}.rb") end @@ -30,12 +30,12 @@ def create_component_file(template) def clear_cache - run 'rm -rf tmp/cache' unless Dir.exist?(File.join('app', 'hyperstack')) + run 'rm -rf tmp/cache' unless Dir.exist?(Rails.root.join('app', 'hyperstack')) end def insure_hyperstack_loader_installed hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} - application_js = File.join( + application_js = Rails.root.join( 'app', 'assets', 'javascripts', 'application.js' ) if File.exist? application_js @@ -70,7 +70,7 @@ def insure_hyperstack_loader_installed " ***********************************************************\n" application_pack_tag = /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/ - Dir.glob(File.join('app', 'views', '**', '*.erb')) do |file| + Dir.glob(Rails.root.join('app', 'views', '**', '*.erb')) do |file| if File.foreach(file).any? { |l| l =~ application_pack_tag } inject_into_file file, after: application_pack_tag do "\n <%= javascript_include_tag 'application' %>" @@ -82,7 +82,7 @@ def insure_hyperstack_loader_installed def insure_base_component_class_exists @component_base_class = options['base-class'] || Hyperstack.component_base_class - file_name = File.join( + file_name = Rails.root.join( 'app', 'hyperstack', 'components', "#{@component_base_class.underscore}.rb" ) template 'hyper_component_template.rb', file_name unless File.exist? file_name diff --git a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb index b8b21cb6d..d55bc6088 100644 --- a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb +++ b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator_base.rb @@ -11,12 +11,12 @@ def warnings end def clear_cache - run 'rm -rf tmp/cache' unless Dir.exists?(File.join('app', 'hyperstack')) + run 'rm -rf tmp/cache' unless Dir.exists?(Rails.root.join('app', 'hyperstack')) end def insure_hyperstack_loader_installed hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+} - application_js = File.join( + application_js = Rails.root.join( 'app', 'assets', 'javascripts', 'application.js' ) if File.exist? application_js @@ -51,7 +51,7 @@ def insure_hyperstack_loader_installed " ***********************************************************\n" application_pack_tag = /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/ - Dir.glob(File.join('app', 'views', '**', '*.erb')) do |file| + Dir.glob(Rails.root.join('app', 'views', '**', '*.erb')) do |file| if File.foreach(file).any? { |l| l =~ application_pack_tag } inject_into_file file, after: application_pack_tag do "\n <%= javascript_include_tag 'application' %>" @@ -63,7 +63,7 @@ def insure_hyperstack_loader_installed def insure_base_component_class_exists @component_base_class = options['base-class'] - file_name = File.join( + file_name = Rails.root.join( 'app', 'hyperstack', 'components', "#{@component_base_class.underscore}.rb" ) template 'hyper_component_template.rb', file_name unless File.exists? file_name diff --git a/ruby/rails-hyperstack/spec/gems.rb b/ruby/rails-hyperstack/spec/gems.rb index 6cd94d1d6..2f8d7cba1 100644 --- a/ruby/rails-hyperstack/spec/gems.rb +++ b/ruby/rails-hyperstack/spec/gems.rb @@ -1,4 +1,3 @@ - gem 'rails-hyperstack', path: '../..' gem 'hyperstack-config', path: '../../../hyperstack-config' gem 'hyper-state', path: '../../../hyper-state' @@ -7,3 +6,5 @@ gem 'hyper-model', path: '../../../hyper-model' gem 'hyper-router', path: '../../../hyper-router' gem 'hyper-spec', path: '../../../hyper-spec' + +gem 'pry' From 81ce343e3f966dfa6d84996f029b91f928c7cc3d Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 12:51:43 -0500 Subject: [PATCH 044/307] trying to run rails-hyperstack specs in dev mode --- .travis.yml | 74 +++++-------- ruby/rails-hyperstack/spec/Gemfile | 1 - ruby/rails-hyperstack/spec/Gemfile.lock | 134 ------------------------ 3 files changed, 25 insertions(+), 184 deletions(-) delete mode 100644 ruby/rails-hyperstack/spec/Gemfile delete mode 100644 ruby/rails-hyperstack/spec/Gemfile.lock diff --git a/.travis.yml b/.travis.yml index fdcbe1270..0da4fc540 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,55 +55,31 @@ _deploy_gem: &_deploy_gem jobs: include: - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_ENV='development' - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_ENV='development' OPAL_VERSION='~>0.11' - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_ENV='development' RAILS_VERSION='~>5.0' - - <<: *_deploy_gem - env: COMPONENT=hyper-i18n - - <<: *_deploy_gem - env: COMPONENT=hyper-trace - - <<: *_deploy_gem - env: COMPONENT=hyper-state - - <<: *_deploy_gem - env: COMPONENT=hyper-component - - <<: *_deploy_gem - env: COMPONENT=hyper-model - - <<: *_deploy_gem - env: COMPONENT=hyper-operation - - <<: *_deploy_gem - env: COMPONENT=hyper-router - - <<: *_deploy_gem - env: COMPONENT=hyper-spec - - <<: *_deploy_gem - env: COMPONENT=hyper-store - - <<: *_deploy_gem - env: COMPONENT=rails-hyperstack - - <<: *_deploy_gem - env: COMPONENT=hyperstack-config + # - <<: *_deploy_gem + # env: COMPONENT=hyper-i18n + # - <<: *_deploy_gem + # env: COMPONENT=hyper-trace + # - <<: *_deploy_gem + # env: COMPONENT=hyper-state + # - <<: *_deploy_gem + # env: COMPONENT=hyper-component + # - <<: *_deploy_gem + # env: COMPONENT=hyper-model + # - <<: *_deploy_gem + # env: COMPONENT=hyper-operation + # - <<: *_deploy_gem + # env: COMPONENT=hyper-router + # - <<: *_deploy_gem + # env: COMPONENT=hyper-spec + # - <<: *_deploy_gem + # env: COMPONENT=hyper-store + # - <<: *_deploy_gem + # env: COMPONENT=rails-hyperstack + # - <<: *_deploy_gem + # env: COMPONENT=hyperstack-config diff --git a/ruby/rails-hyperstack/spec/Gemfile b/ruby/rails-hyperstack/spec/Gemfile deleted file mode 100644 index 33f205482..000000000 --- a/ruby/rails-hyperstack/spec/Gemfile +++ /dev/null @@ -1 +0,0 @@ -gem 'rails', '~> 6.0' diff --git a/ruby/rails-hyperstack/spec/Gemfile.lock b/ruby/rails-hyperstack/spec/Gemfile.lock deleted file mode 100644 index 5d3622983..000000000 --- a/ruby/rails-hyperstack/spec/Gemfile.lock +++ /dev/null @@ -1,134 +0,0 @@ -GEM - specs: - actioncable (6.0.2.1) - actionpack (= 6.0.2.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailbox (6.0.2.1) - actionpack (= 6.0.2.1) - activejob (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) - mail (>= 2.7.1) - actionmailer (6.0.2.1) - actionpack (= 6.0.2.1) - actionview (= 6.0.2.1) - activejob (= 6.0.2.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.0.2.1) - actionview (= 6.0.2.1) - activesupport (= 6.0.2.1) - rack (~> 2.0, >= 2.0.8) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.2.1) - actionpack (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) - nokogiri (>= 1.8.5) - actionview (6.0.2.1) - activesupport (= 6.0.2.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.2.1) - activesupport (= 6.0.2.1) - globalid (>= 0.3.6) - activemodel (6.0.2.1) - activesupport (= 6.0.2.1) - activerecord (6.0.2.1) - activemodel (= 6.0.2.1) - activesupport (= 6.0.2.1) - activestorage (6.0.2.1) - actionpack (= 6.0.2.1) - activejob (= 6.0.2.1) - activerecord (= 6.0.2.1) - marcel (~> 0.3.1) - activesupport (6.0.2.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2) - builder (3.2.4) - concurrent-ruby (1.1.6) - crass (1.0.6) - erubi (1.9.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.8.2) - concurrent-ruby (~> 1.0) - loofah (2.4.0) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mimemagic (0.3.4) - mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.14.0) - nio4r (2.5.2) - nokogiri (1.10.9) - mini_portile2 (~> 2.4.0) - rack (2.2.2) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.0.2.1) - actioncable (= 6.0.2.1) - actionmailbox (= 6.0.2.1) - actionmailer (= 6.0.2.1) - actionpack (= 6.0.2.1) - actiontext (= 6.0.2.1) - actionview (= 6.0.2.1) - activejob (= 6.0.2.1) - activemodel (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) - bundler (>= 1.3.0) - railties (= 6.0.2.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (6.0.2.1) - actionpack (= 6.0.2.1) - activesupport (= 6.0.2.1) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) - rake (13.0.1) - sprockets (4.0.0) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - thor (1.0.1) - thread_safe (0.3.6) - tzinfo (1.2.6) - thread_safe (~> 0.1) - websocket-driver (0.7.1) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.4) - zeitwerk (2.3.0) - -PLATFORMS - ruby - -DEPENDENCIES - rails (~> 6.0) - -BUNDLED WITH - 2.0.2 From b9177ce7cdda685ec8e1e0521d767748af1f6175 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 18:06:52 -0500 Subject: [PATCH 045/307] removed dependency on rails cache for testing --- .travis.yml | 50 +++++++++---------- .../active_record/queued_message.rb | 8 +-- .../transport/hyperstack_controller.rb | 4 ++ ruby/hyper-spec/hyper-spec.gemspec | 1 + .../lib/hyper-spec/component_test_helpers.rb | 39 +++++++++++++-- ruby/rails-hyperstack/Gemfile | 3 +- ruby/rails-hyperstack/Rakefile | 3 +- 7 files changed, 73 insertions(+), 35 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0da4fc540..1ef1e0ec3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,31 +55,31 @@ _deploy_gem: &_deploy_gem jobs: include: - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_ENV='development' + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_ENV='development' OPAL_VERSION='~>0.11' + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_ENV='development' RAILS_VERSION='~>5.0' + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_deploy_gem - # env: COMPONENT=hyper-i18n - # - <<: *_deploy_gem - # env: COMPONENT=hyper-trace - # - <<: *_deploy_gem - # env: COMPONENT=hyper-state - # - <<: *_deploy_gem - # env: COMPONENT=hyper-component - # - <<: *_deploy_gem - # env: COMPONENT=hyper-model - # - <<: *_deploy_gem - # env: COMPONENT=hyper-operation - # - <<: *_deploy_gem - # env: COMPONENT=hyper-router - # - <<: *_deploy_gem - # env: COMPONENT=hyper-spec - # - <<: *_deploy_gem - # env: COMPONENT=hyper-store - # - <<: *_deploy_gem - # env: COMPONENT=rails-hyperstack - # - <<: *_deploy_gem - # env: COMPONENT=hyperstack-config + - <<: *_deploy_gem + env: COMPONENT=hyper-i18n + - <<: *_deploy_gem + env: COMPONENT=hyper-trace + - <<: *_deploy_gem + env: COMPONENT=hyper-state + - <<: *_deploy_gem + env: COMPONENT=hyper-component + - <<: *_deploy_gem + env: COMPONENT=hyper-model + - <<: *_deploy_gem + env: COMPONENT=hyper-operation + - <<: *_deploy_gem + env: COMPONENT=hyper-router + - <<: *_deploy_gem + env: COMPONENT=hyper-spec + - <<: *_deploy_gem + env: COMPONENT=hyper-store + - <<: *_deploy_gem + env: COMPONENT=rails-hyperstack + - <<: *_deploy_gem + env: COMPONENT=hyperstack-config diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb index 8781f90d6..363dc8e11 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record/queued_message.rb @@ -15,15 +15,17 @@ class QueuedMessage < ::ActiveRecord::Base serialize :data belongs_to :hyperstack_connection, - class_name: 'Hyperstack::ConnectionAdapter::ActiveRecord::Connection', - foreign_key: 'connection_id' + class_name: 'Hyperstack::ConnectionAdapter::ActiveRecord::Connection', + foreign_key: 'connection_id', + optional: true scope :for_session, ->(session) { joins(:hyperstack_connection).where('session = ?', session) } # For simplicity we use QueuedMessage with connection_id 0 # to store the current path which is used by consoles to - # communicate back to the server + # communicate back to the server. The belongs_to connection + # therefore must be optional. default_scope { where('connection_id IS NULL OR connection_id != 0') } diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb index 26f7685c5..da95eb103 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/hyperstack_controller.rb @@ -11,6 +11,10 @@ def acceptable_content_type?(headers) end end if defined? ::WebConsole::Middleware + + # The purpose of this is to prevent massive amounts of logging + # if using simple polling. If polling is on then only actual messages + # with content will be shown, other wise the log message is dropped. module ::Rails module Rack class Logger < ActiveSupport::LogSubscriber diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 9788999cf..4ca0a69eb 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'capybara' spec.add_dependency 'chromedriver-helper', '1.2.0' + spec.add_dependency 'filecache' spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'method_source' spec.add_dependency 'mini_racer', '~> 0.2.6' diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index f29d5fcef..5015143a1 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -4,6 +4,7 @@ require 'hyper-spec/unparser_patch' require 'method_source' require_relative '../../lib/hyper-spec/time_cop.rb' +require 'filecache' Parser::Builders::Default.emit_procarg0 = true if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 @@ -27,6 +28,36 @@ def display_example_description "" end + + RAILS_CACHE = false + + def file_cache + @file_cache ||= FileCache.new("cache", "/tmp/hyper-spec-caches", 30, 3) + end + + def cache_read(key) + if RAILS_CACHE + ::Rails.cache.read(key) + else + file_cache.get(key) + end + end + + def cache_write(key, value) + if RAILS_CACHE + ::Rails.cache.write(key, value) + else + file_cache.set(key, value) + end + end + + def cache_delete(key) + if RAILS_CACHE + ::Rails.cache.write(key, value) + else + file_cache.delete(key) + end + end end # TODO: verify this works in all cases... @@ -60,7 +91,7 @@ def build_test_url_for(controller) controller.class_eval do define_method(:test) do route_root = self.class.name.gsub(/Controller$/, '').underscore - test_params = ::Rails.cache.read("/#{route_root}/#{params[:id]}") + test_params = ComponentTestHelpers.cache_read("/#{route_root}/#{params[:id]}") @component_name = test_params[0] @component_params = test_params[1] render_params = test_params[2] @@ -255,16 +286,16 @@ class Hyperstack::Internal::Component::TestDummy end component_name ||= 'Hyperstack::Internal::Component::TestDummy' - ::Rails.cache.write(test_url, [component_name, params, opts]) + ComponentTestHelpers.cache_write(test_url, [component_name, params, opts]) test_code_key = "hyper_spec_prerender_test_code.js" @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] if opts[:render_on] == :both || opts[:render_on] == :server_only unless opts[:code].blank? - ::Rails.cache.write(test_code_key, opts[:code]) + ComponentTestHelpers.cache_write(test_code_key, opts[:code]) ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + [test_code_key] ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes else - ::Rails.cache.delete(test_code_key) + ComponentTestHelpers.cache_delete(test_code_key) ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes end diff --git a/ruby/rails-hyperstack/Gemfile b/ruby/rails-hyperstack/Gemfile index 28d6764fb..4c3967aa3 100644 --- a/ruby/rails-hyperstack/Gemfile +++ b/ruby/rails-hyperstack/Gemfile @@ -6,6 +6,5 @@ gem 'hyper-operation', path: '../hyper-operation' gem 'hyper-model', path: '../hyper-model' gem 'hyper-router', path: '../hyper-router' gem 'hyper-spec', path: '../hyper-spec' -# gem 'rails', '~> 5.2' -gem 'webpacker' +gem 'webpacker' # TODO: figure out why this is necessary!gi gemspec diff --git a/ruby/rails-hyperstack/Rakefile b/ruby/rails-hyperstack/Rakefile index 19f4b49c3..38da6d1f5 100644 --- a/ruby/rails-hyperstack/Rakefile +++ b/ruby/rails-hyperstack/Rakefile @@ -23,7 +23,8 @@ namespace :spec do sh('bundle exec rails generate model Sample name:string description:text') sh('mv app/models/sample.rb app/hyperstack/models/sample.rb') sh('bundle exec rake db:migrate') - sh('bundle exec rails dev:cache') # not tested yet... + sh('RAILS_ENV=test bundle exec rake db:setup') + # sh('bundle exec rails dev:cache') # not tested yet... end end end From ff09f484833aec893983a9a158fdc115830d387e Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 18:09:41 -0500 Subject: [PATCH 046/307] travis whoops --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1ef1e0ec3..27fef12d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,6 @@ jobs: env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - <<: *_test_gem env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_deploy_gem env: COMPONENT=hyper-i18n - <<: *_deploy_gem From 7b36c4d95bcae43c952b146d78c3873c7482e7a8 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 18:16:06 -0500 Subject: [PATCH 047/307] travis whoops redeux --- .travis.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.travis.yml b/.travis.yml index 27fef12d4..ecc557fe5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,6 +60,31 @@ jobs: env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - <<: *_test_gem env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + - <<: *_deploy_gem env: COMPONENT=hyper-i18n - <<: *_deploy_gem From 34e48ab06ded8c1042b2af8106a73c51b4c0a02a Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 18:49:58 -0500 Subject: [PATCH 048/307] few more fixes for regressions from last change --- .../component/rails/server_rendering/hyper_asset_container.rb | 2 +- ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb | 2 ++ ruby/rails-hyperstack/Gemfile | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb index bf9ce120a..2b9dfbb5a 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb @@ -9,7 +9,7 @@ module Rails module ServerRendering class HyperTestAssetContainer def find_asset(logical_path) - ::Rails.cache.read(logical_path) + HyperSpec::ComponentTestHelpers.cache_read(logical_path) end end diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 5015143a1..3dd8a3f5a 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -57,6 +57,8 @@ def cache_delete(key) else file_cache.delete(key) end + rescue StandardError + nil end end diff --git a/ruby/rails-hyperstack/Gemfile b/ruby/rails-hyperstack/Gemfile index 4c3967aa3..cbc05abe0 100644 --- a/ruby/rails-hyperstack/Gemfile +++ b/ruby/rails-hyperstack/Gemfile @@ -6,5 +6,6 @@ gem 'hyper-operation', path: '../hyper-operation' gem 'hyper-model', path: '../hyper-model' gem 'hyper-router', path: '../hyper-router' gem 'hyper-spec', path: '../hyper-spec' -gem 'webpacker' # TODO: figure out why this is necessary!gi +gem 'webpacker' # TODO: figure out why these two are necessary! +gem 'turbolinks' gemspec From f69004ccb6e3ea970e6c76cdf1ec1c7e550bfdbb Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 13 Jan 2021 22:20:16 -0500 Subject: [PATCH 049/307] CI for dependency variations --- .travis.yml | 58 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ecc557fe5..9927bc5af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,10 +56,6 @@ jobs: include: - <<: *_test_gem env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - <<: *_test_gem env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - <<: *_test_gem @@ -85,6 +81,60 @@ jobs: - <<: *_test_gem env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_deploy_gem env: COMPONENT=hyper-i18n - <<: *_deploy_gem From 04c9272c4022757103e21512c6c8290db7daec91 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 15 Jan 2021 09:54:02 -0500 Subject: [PATCH 050/307] first attempt using new hyperspec syntax --- .travis.yml | 108 ++--- ruby/hyper-spec/Gemfile | 1 - ruby/hyper-spec/bin/console | 0 ruby/hyper-spec/bin/setup | 0 ruby/hyper-spec/hyper-spec.gemspec | 2 +- ruby/hyper-spec/lib/hyper-spec.rb | 122 +++-- .../lib/hyper-spec/component_test_helpers.rb | 432 +++++++++++++++--- ruby/hyper-spec/lib/hyper-spec/time_cop.rb | 12 + .../lib/hyper-spec/wait_for_ajax.rb | 2 +- ruby/hyper-spec/spec/hyper_spec.rb | 60 ++- .../spec/test_app/app/views/components.rb | 1 + ruby/hyper-spec/spec/test_app/bin/bundle | 0 ruby/hyper-spec/spec/test_app/bin/rails | 0 ruby/hyper-spec/spec/test_app/bin/rake | 0 ruby/hyper-spec/spec/test_app/bin/setup | 0 15 files changed, 565 insertions(+), 175 deletions(-) mode change 100755 => 100644 ruby/hyper-spec/bin/console mode change 100755 => 100644 ruby/hyper-spec/bin/setup mode change 100755 => 100644 ruby/hyper-spec/spec/test_app/bin/bundle mode change 100755 => 100644 ruby/hyper-spec/spec/test_app/bin/rails mode change 100755 => 100644 ruby/hyper-spec/spec/test_app/bin/rake mode change 100755 => 100644 ruby/hyper-spec/spec/test_app/bin/setup diff --git a/.travis.yml b/.travis.yml index 9927bc5af..3e930dd6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,60 +80,60 @@ jobs: env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 - <<: *_test_gem env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - <<: *_deploy_gem env: COMPONENT=hyper-i18n diff --git a/ruby/hyper-spec/Gemfile b/ruby/hyper-spec/Gemfile index d30e6f364..c0e97c691 100644 --- a/ruby/hyper-spec/Gemfile +++ b/ruby/hyper-spec/Gemfile @@ -1,5 +1,4 @@ source 'https://rubygems.org' -#gem 'opal', '< 1.0' gem 'hyper-component', path: '../hyper-component' gem 'hyper-store', path: '../hyper-store' gem 'hyper-state', path: '../hyper-state' diff --git a/ruby/hyper-spec/bin/console b/ruby/hyper-spec/bin/console old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/bin/setup b/ruby/hyper-spec/bin/setup old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 4ca0a69eb..d1af270f0 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'method_source' spec.add_dependency 'mini_racer', '~> 0.2.6' spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' - spec.add_dependency 'parser', '>= 2.3' + spec.add_dependency 'parser', '>= 2.3.3.1' # on rails-6 this is now >= 2.3 spec.add_dependency 'rspec-rails' spec.add_dependency 'selenium-webdriver' spec.add_dependency 'timecop', '~> 0.8.1' diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index c529ccf46..1f2c76353 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -7,8 +7,71 @@ require 'hyper-spec/wait_for_ajax' require 'selenium/web_driver/firefox/profile' +module HyperSpec + def self.reset_between_examples=(value) + RSpec.configuration.reset_between_examples = value + end + + def self.reset_between_examples? + RSpec.configuration.reset_between_examples + end + + def self.reset_sessions! + Capybara.old_reset_sessions! + end +end + +# TODO: figure out why we need this patch - its because we are on an old version of Selenium Webdriver, but why? +require 'selenium-webdriver' + +module Selenium + module WebDriver + module Chrome + module Bridge + COMMANDS = remove_const(:COMMANDS).dup + COMMANDS[:get_log] = [:post, 'session/:session_id/log'] + COMMANDS.freeze + + def log(type) + data = execute :get_log, {}, {type: type.to_s} + + Array(data).map do |l| + begin + LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message') + rescue KeyError + next + end + end + end + end + end + end +end + + +module Capybara + class << self + alias_method :old_reset_sessions!, :reset_sessions! + def reset_sessions! + old_reset_sessions! if HyperSpec.reset_between_examples? + end + end +end + RSpec.configure do |config| + config.add_setting :reset_between_examples, default: true + config.before(:all, no_reset: true) do + HyperSpec.reset_between_examples = false + end + config.after(:all) do + HyperSpec.reset_sessions! unless HyperSpec.reset_between_examples? + end + config.before(:each) do |example| + insure_page_loaded(true) if example.metadata[:js] && !HyperSpec.reset_between_examples? + end +end +RSpec.configure do |config| config.include HyperSpec::ComponentTestHelpers config.include HyperSpec::WaitForAjax config.include Capybara::DSL @@ -17,6 +80,7 @@ config.add_setting :debugger_width, default: nil + config.before(:each) do Hyperstack.class_eval do def self.on_server? @@ -44,40 +108,13 @@ def self.on_server? PusherFake::Channel.reset if defined? PusherFake end end - -end - -# TODO: figure out why we need this patch - its because we are on an old version of Selenium Webdriver, but why? -require 'selenium-webdriver' - -module Selenium - module WebDriver - module Chrome - module Bridge - COMMANDS = remove_const(:COMMANDS).dup - COMMANDS[:get_log] = [:post, 'session/:session_id/log'] - COMMANDS.freeze - - def log(type) - data = execute :get_log, {}, {type: type.to_s} - - Array(data).map do |l| - begin - LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message') - rescue KeyError - next - end - end - end - end - end - end end # Capybara config RSpec.configure do |config| config.add_setting :wait_for_initialization_time config.wait_for_initialization_time = 3 + Capybara.default_max_wait_time = 10 Capybara.register_driver :chrome do |app| @@ -88,17 +125,28 @@ def log(type) #prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } #) unless ENV['NO_DEBUGGER'] # this does not seem to work properly. Don't document this feature yet. - #options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] + # options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] + options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options, 'goog:loggingPrefs' => {browser: 'ALL'}) - # Capybara::Selenium::Driver.new(app, :browser => :chrome, desired_capabilities: { - # "chromeOptions" => { - # w3c: false - # }, - # 'goog:loggingPrefs' => {browser: 'ALL'} - # }) Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) end + # Capybara.register_driver :chrome do |app| + # options = {} + # options.merge!( + # args: %w[auto-open-devtools-for-tabs], + # prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } + # ) unless ENV['NO_DEBUGGER'] + # # this does not seem to work properly. Don't document this feature yet. + # options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] + # capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) + # Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) + # end + + Capybara.register_driver :firefox do |app| + Capybara::Selenium::Driver.new(app, browser: :firefox) + end + Capybara.register_driver :chrome_headless_docker_travis do |app| options = ::Selenium::WebDriver::Chrome::Options.new options.add_argument('--headless') @@ -107,10 +155,6 @@ def log(type) Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options) end - Capybara.register_driver :firefox do |app| - Capybara::Selenium::Driver.new(app, browser: :firefox) - end - Capybara.register_driver :firefox_headless do |app| options = Selenium::WebDriver::Firefox::Options.new options.headless! diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 3dd8a3f5a..bfca0ecd6 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -1,18 +1,81 @@ # see component_test_helpers_spec.rb for examples require 'parser/current' require 'unparser' -require 'hyper-spec/unparser_patch' +require 'hyper-spec/unparser_patch' # not present in original version of refactored hyperspec require 'method_source' +require 'pry' require_relative '../../lib/hyper-spec/time_cop.rb' require 'filecache' -Parser::Builders::Default.emit_procarg0 = true +Parser::Builders::Default.emit_procarg0 = true # not present in original version of refactored hyperspec if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 Parser::Builders::Default.emit_arg_inside_procarg0 = true # not available in parser 2.3 end +module MethodSource + class << self + alias original_lines_for_before_hyper_spec lines_for + alias original_source_helper_before_hyper_spec source_helper + + def source_helper(source_location, name=nil) + source_location[1] = 1 if source_location[0] == '(pry)' + original_source_helper_before_hyper_spec source_location, name + end + + def lines_for(file_name, name = nil) + if file_name == '(pry)' + HyperSpec.current_pry_code_block + else + original_lines_for_before_hyper_spec file_name, name + end + end + end +end + +class Object + def to_opal_expression + to_json + rescue Exception + to_s + end +end + +class Time + def to_opal_expression + "Time.parse('#{inspect}')" + end +end + +class NilClass + def to_opal_expression + self.inspect + end +end + module HyperSpec + + # add a before eval hook to pry so we can capture the source + + if defined? Pry + class << self + attr_accessor :current_pry_code_block + Pry.hooks.add_hook(:before_eval, "hyper_spec_code_capture") do |code| + HyperSpec.current_pry_code_block = code + end + end + end + module ComponentTestHelpers + def self.opal_compile(s) + Opal.compile(s) + rescue Exception => e + puts "puts could not compile: \n\n#{s}\n\n" + raise e + end + + def opal_compile(s) + ComponentTestHelpers.opal_compile(s) + end TOP_LEVEL_COMPONENT_PATCH = Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) @@ -20,15 +83,20 @@ module ComponentTestHelpers Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" + class << self attr_accessor :current_example attr_accessor :description_displayed + def test_id + @_hyperspec_private_test_id ||= 0 + @_hyperspec_private_test_id += 1 + end + def display_example_description "" end - RAILS_CACHE = false def file_cache @@ -78,13 +146,13 @@ def new_controller end end - def build_test_url_for(controller) + def build_test_url_for(controller, ping = nil) unless controller - unless defined?(::HyperstackTestController) - Object.const_set('HyperstackTestController', new_controller) + unless defined?(::HyperSpecTestController) + Object.const_set('HyperSpecTestController', new_controller) end - controller = ::HyperstackTestController + controller = ::HyperSpecTestController end route_root = controller.name.gsub(/Controller$/, '').underscore @@ -92,21 +160,27 @@ def build_test_url_for(controller) unless controller.method_defined?(:test) controller.class_eval do define_method(:test) do + head(:no_content) && return if params[:id] == 'ping' route_root = self.class.name.gsub(/Controller$/, '').underscore - test_params = ComponentTestHelpers.cache_read("/#{route_root}/#{params[:id]}") + key = "/#{route_root}/#{params[:id]}" + test_params = ComponentTestHelpers.cache_read(key) @component_name = test_params[0] @component_params = test_params[1] - render_params = test_params[2] + html_block = test_params[2] + render_params = test_params[3] render_on = render_params.delete(:render_on) || :client_only _mock_time = render_params.delete(:mock_time) style_sheet = render_params.delete(:style_sheet) javascript = render_params.delete(:javascript) code = render_params.delete(:code) + #page = "#{html_block}\n\n" + page = "\n" page = '<%= react_component @component_name, @component_params, '\ - "{ prerender: #{render_on != :client_only} } %>" + "{ prerender: #{render_on != :client_only} } %>\n#{page}" if @component_name + #page = "\n#{page}" unless render_on == :server_only - page = "\n#{page}" + page = "\n#{page}" if @component_name page = "\n#{page}" if code end @@ -124,14 +198,20 @@ def build_test_url_for(controller) page = "\n#{page}" - title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) - title = "#{title}...continued." if ComponentTestHelpers.description_displayed + if ComponentTestHelpers.current_example + + title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) + title = "#{title}...continued." if ComponentTestHelpers.description_displayed - page = "\n#{page}" + page = "\n#{page}" - ComponentTestHelpers.description_displayed = true + ComponentTestHelpers.description_displayed = true + end + page = "\n#{html_block}\n#{page}" render_params[:inline] = page + response.headers['Cache-Control'] = 'max-age=120' + response.headers['X-Tracking-ID'] = '123456' render render_params end end @@ -150,50 +230,153 @@ def build_test_url_for(controller) routes.disable_clear_and_finalize = false end end + if ping + "/#{route_root}/ping" + else + "/#{route_root}/#{ComponentTestHelpers.test_id}" + end + end - "/#{route_root}/#{@test_id = (@test_id || 0) + 1}" + def insert_html(str) + @_hyperspec_private_html_block = "#{@_hyperspec_private_html_block}\n#{str}" end def isomorphic(&block) yield - on_client(&block) + before_mount(&block) end - def evaluate_ruby(str = '', opts = {}, &block) + def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) + insure_page_loaded + # TODO: better error message here...either you give us a block + # or first argument must be a hash or a string. + if p1.is_a? Hash + str = '' + p3 = p2 + p2 = p1 + else + str = p1 + end + if p3 + opts = p2 + args = p3 + elsif p2 + opts = {} + args = p2 + else + opts = args = {} + end + + args.each do |name, value| + str = "#{name} = #{value.to_opal_expression}\n#{str}" + end + str = add_opal_block(str, block) if block + js = opal_compile(str).delete("\n").gsub('(Opal);', '(Opal)') # workaround for firefox 58 and geckodriver 0.19.1, because firefox is unable to find .$to_json: # JSON.parse(evaluate_script("(function(){var a=Opal.Array.$new(); a[0]=#{js}; return a.$to_json();})();"), opts).first - insure_mount - JSON.parse(evaluate_script("[#{compile_to_ruby(str, &block)}].$to_json()"), opts).first + JSON.parse(evaluate_script("[#{js}].$to_json()"), opts).first + end + + alias c? evaluate_ruby + + # TODO: add a compile_ruby method. refactor evaluate_ruby and expect_evaluate ruby to use common methods + # that process the params, and produce a ruby code string, and a resulting JS string + # compile_ruby can be useful in seeing what code opal produces... + + def expect_evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) + insure_page_loaded + if p1.is_a? Hash + str = '' + p3 = p2 + p2 = p1 + else + str = p1 + end + if p3 + opts = p2 + args = p3 + elsif p2 + opts = {} + args = p2 + else + opts = args = {} + end + args.each do |name, value| + str = "#{name} = #{value.inspect}\n#{str}" + end + expect(evaluate_ruby(add_opal_block(str, block), opts, {})) + end + + PRIVATE_VARIABLES = %i[ + @__inspect_output @__memoized @example @_hyperspec_private_client_code @_hyperspec_private_html_block @fixture_cache + @fixture_connections @connection_subscriber @loaded_fixtures @_hyperspec_private_client_options + b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_ + ] + + def add_locals(in_str, block) + b = block.binding + + memoized = b.eval('__memoized').instance_variable_get(:@memoized) + in_str = memoized.inject(in_str) do |str, pair| + "#{str}\n#{pair.first} = #{pair.last.to_opal_expression}" + end if memoized + + in_str = b.local_variables.inject(in_str) do |str, var| + next str if PRIVATE_VARIABLES.include? var + + "#{str}\n#{var} = #{b.local_variable_get(var).to_opal_expression}" + end + + in_str = b.eval('instance_variables').inject(in_str) do |str, var| + next str if PRIVATE_VARIABLES.include? var + + "#{str}\n#{var} = #{b.eval("instance_variable_get('#{var}')").to_opal_expression}" + end + in_str end - # handy for debugging extreme issues where we want to see what the compiler actually generates - def compile_to_ruby(str = '', &block) - if block - str = "#{str}\n#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}" + def find_block(node) + # find a block with the ast tree. + + return false unless node.class == Parser::AST::Node + return node if the_node_you_are_looking_for?(node) + + node.children.each do |child| + found = find_block(child) + return found if found end - Opal.compile(str).gsub("// Prepare super implicit arguments\n", "") - .delete("\n").gsub('(Opal);', '(Opal)') + false end - def expect_evaluate_ruby(str = '', opts = {}, &block) - insure_mount - expect(evaluate_ruby(add_opal_block(str, block), opts)) + def the_node_you_are_looking_for?(node) + node.type == :block && + node.children.first.class == Parser::AST::Node && + node.children.first.type == :send + # we could also check that the block is going to the right method + # respond_to?(node.children.first.children[1]) && + # method(node.children.first.children[1]) == method(:evaluate_ruby) + # however that does not work for expect { ... }.on_client_to ... + # because now the block is being sent to expect... so we could + # check the above OR node.children.first.children[1] == :expect + # but what if there are two blocks? on and on... end def add_opal_block(str, block) - # big assumption here is that we are going to follow this with a .to - # hence .children.first followed by .children.last - # probably should do some kind of "search" to make this work nicely return str unless block - "#{str}\n"\ - "#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.first.children.last}" + + source = block.source + ast = Parser::CurrentRuby.parse(source) + ast = find_block(ast) + raise "could not find block within source: #{block.source}" unless ast + + "#{add_locals(str, block)}\n#{Unparser.unparse ast.children.last}" end - def evaluate_promise(str = '', opts = {}, &block) - insure_mount - str = "#{str}\n#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}" if block - str = "#{str}.then { |args| args = [args]; `window.hyper_spec_promise_result = args` }" - js = Opal.compile(str).gsub("\n","").gsub("(Opal);","(Opal)") + def evaluate_promise(str = '', opts = {}, _dummy = nil, &block) + insure_page_loaded + str = add_opal_block(str, block) + str = "(#{str}).then { |args| args = [args]; `window.hyper_spec_promise_result = args` }" + js = opal_compile(str).gsub("\n","").gsub("(Opal);","(Opal)") page.evaluate_script("window.hyper_spec_promise_result = false") page.execute_script(js) Timeout.timeout(Capybara.default_max_wait_time) do @@ -205,34 +388,46 @@ def evaluate_promise(str = '', opts = {}, &block) JSON.parse(page.evaluate_script("window.hyper_spec_promise_result.$to_json()"), opts).first end + alias promise? evaluate_promise + def expect_promise(str = '', opts = {}, &block) - insure_mount expect(evaluate_promise(add_opal_block(str, block), opts)) end def ppr(str) - js = Opal.compile(str).delete("\n").gsub('(Opal);', '(Opal)') + js = opal_compile(str).delete("\n").gsub('(Opal);', '(Opal)') execute_script("console.log(#{js})") end - def on_client(&block) - @client_code = - "#{@client_code}#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}\n" + def before_mount(&block) # was called on_client + @_hyperspec_private_client_code = + "#{@_hyperspec_private_client_code}#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}\n" end + # to get legacy on_client behavior you can alias on_client before_mount + + alias on_client evaluate_ruby + def debugger `debugger` nil end - def insure_mount - # rescue in case page is not defined... - mount unless page.instance_variable_get('@hyper_spec_mounted') + def insure_page_loaded(only_if_code_or_html_exists = nil) + return if only_if_code_or_html_exists && !@_hyperspec_private_client_code && !@_hyperspec_private_html_block + # if we are not resetting between examples, or think its mounted + # then look for Opal, but if we can't find it, then ping to clear and try again + if !HyperSpec.reset_between_examples? || page.instance_variable_get('@hyper_spec_mounted') + r = evaluate_script('Opal && true') rescue nil + return if r + page.visit build_test_url_for(nil, true) rescue nil + end + load_page end def client_option(opts = {}) - @client_options ||= {} - @client_options.merge! opts + @_hyperspec_private_client_options ||= {} + @_hyperspec_private_client_options.merge! opts end alias client_options client_option @@ -245,8 +440,7 @@ def mount(component_name = nil, params = nil, opts = {}, &block) opts = client_options opts test_url = build_test_url_for(opts.delete(:controller)) - - if block || @client_code || component_name.nil? + if block || @_hyperspec_private_client_code || component_name.nil? block_with_helpers = <<-code module ComponentHelpers def self.js_eval(s) @@ -277,37 +471,47 @@ def self.add_class(class_name, styles={}) } end end - class Hyperstack::Internal::Component::TestDummy - include Hyperstack::Component - render {} - end - #{@client_code} + # class React::Component::HyperTestDummy < React::Component::Base + # def render; end + # end + #{@_hyperspec_private_client_code} #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} code - opts[:code] = Opal.compile(block_with_helpers) + opts[:code] = opal_compile(block_with_helpers) end - - component_name ||= 'Hyperstack::Internal::Component::TestDummy' - ComponentTestHelpers.cache_write(test_url, [component_name, params, opts]) + @_hyperspec_private_client_code = nil + #component_name ||= 'React::Component::HyperTestDummy' + value = [component_name, params, @_hyperspec_private_html_block, opts] + ComponentTestHelpers.cache_write(test_url, value) + @_hyperspec_private_html_block = nil test_code_key = "hyper_spec_prerender_test_code.js" - @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] - if opts[:render_on] == :both || opts[:render_on] == :server_only - unless opts[:code].blank? - ComponentTestHelpers.cache_write(test_code_key, opts[:code]) - ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + [test_code_key] - ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes - else - ComponentTestHelpers.cache_delete(test_code_key) - ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files - ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes + if defined? ::Hyperstack::Component # was ::React in old hyperloop version + @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] + if opts[:render_on] == :both || opts[:render_on] == :server_only + unless opts[:code].blank? + ComponentTestHelpers.cache_write(test_code_key, opts[:code]) + ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + [test_code_key] + ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes + else + ComponentTestHelpers.cache_delete(test_code_key) + ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes + end end end + page.instance_variable_set('@hyper_spec_mounted', false) visit test_url wait_for_ajax unless opts[:no_wait] page.instance_variable_set('@hyper_spec_mounted', true) Lolex.init(self, client_options[:time_zone], client_options[:clock_resolution]) end + def load_page + mount + end + + alias reload_page load_page + [:callback_history_for, :last_callback_for, :clear_callback_history_for, :event_history_for, :last_event_for, :clear_event_history_for].each do |method| define_method(method) do |event_name| @@ -316,18 +520,19 @@ class Hyperstack::Internal::Component::TestDummy end def run_on_client(&block) - script = Opal.compile(Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last)) + script = opal_compile(Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last)) execute_script(script) end def add_class(class_name, style) - @client_code = "#{@client_code}ComponentHelpers.add_class('#{class_name}', #{style})\n" + @_hyperspec_private_client_code = "#{@_hyperspec_private_client_code}ComponentHelpers.add_class('#{class_name}', #{style})\n" end def attributes_on_client(model) evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes", symbolize_names: true) end + def open_in_chrome if false && ['linux', 'freebsd'].include?(`uname`.downcase) `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}` @@ -343,11 +548,11 @@ def open_in_chrome def pause(message = nil) if message puts message - page.evaluate_script "console.log('#{message} (type go() to continue)')" + page.evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" end page.evaluate_script('window.hyper_spec_waiting_for_go = true') - page.evaluate_script('go = function() {window.hyper_spec_waiting_for_go = false}') + loop do sleep 0.25 break unless page.evaluate_script('window.hyper_spec_waiting_for_go') @@ -394,6 +599,7 @@ def size_window(width = nil, height = nil) end width, height = [height, width] if portrait + unless RSpec.configuration.debugger_width Capybara.current_session.current_window.resize_to(1000, 500) sleep RSpec.configuration.wait_for_initialization_time @@ -416,3 +622,79 @@ def size_window(width = nil, height = nil) end end end + +module RSpec + module Expectations + class ExpectationTarget + end + + module HyperSpecInstanceMethods + + def self.included(base) + base.include HyperSpec::ComponentTestHelpers + end + + def to_on_client(matcher, message = nil, &block) + evaluate_client('ruby').to(matcher, message, &block) + end + + alias on_client_to to_on_client + + def to_on_client_not(matcher, message = nil, &block) + evaluate_client('ruby').not_to(matcher, message, &block) + end + + alias on_client_to_not to_on_client_not + alias on_client_not_to to_on_client_not + alias to_not_on_client to_on_client_not + alias not_to_on_client to_on_client_not + + def to_then(matcher, message = nil, &block) + evaluate_client('promise').to(matcher, message, &block) + end + + alias then_to to_then + + def to_then_not(matcher, message = nil, &block) + evaluate_client('promise').not_to(matcher, message, &block) + end + + alias then_to_not to_then_not + alias then_not_to to_then_not + alias to_not_then to_then_not + alias not_to_then to_then_not + + private + + def evaluate_client(method) + source = add_opal_block(@args_str, @target) + value = @target.binding.eval("evaluate_#{method}(#{source.inspect}, {}, {})") + ExpectationTarget.for(value, nil) + end + end + + class OnClientWithArgsTarget + include HyperSpecInstanceMethods + + def initialize(target, args) + unless args.is_a? Hash + raise ExpectationNotMetError, + "You must pass a hash of local var, value pairs to the 'with' modifier" + end + + @target = target + @args_str = args.collect do |name, value| + "#{name} = #{value.to_opal_expression}" + end.join("\n") + end + end + + class BlockExpectationTarget < ExpectationTarget + include HyperSpecInstanceMethods + + def with(args) + OnClientWithArgsTarget.new(@target, args) + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/time_cop.rb b/ruby/hyper-spec/lib/hyper-spec/time_cop.rb index f5f4fae51..dadda29a4 100644 --- a/ruby/hyper-spec/lib/hyper-spec/time_cop.rb +++ b/ruby/hyper-spec/lib/hyper-spec/time_cop.rb @@ -59,6 +59,10 @@ def create_ticker ticker end + def init(scale: 1, resolution: 10) + update_lolex(Time.now, scale, resolution) + end + def update_lolex(time, scale, resolution) `#{@lolex}.uninstall()` && return if scale.nil? @mock_start_time = time.to_f * 1000 @@ -80,6 +84,14 @@ def update_lolex(time, scale, resolution) end end + # create an alias for Lolex.init so we can say Timecop.init on the client + + class Timecop + def self.init(*args) + Lolex.init(*args) + end + end + else require 'timecop' diff --git a/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb b/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb index 9d604582a..a58671f55 100644 --- a/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb +++ b/ruby/hyper-spec/lib/hyper-spec/wait_for_ajax.rb @@ -29,7 +29,7 @@ def running? } })(); CODE - page.evaluate_script(jscode) + Capybara.page.evaluate_script(jscode) rescue Exception => e puts "wait_for_ajax failed while testing state of ajax requests: #{e}" end diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 2323236fc..148836f84 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -10,7 +10,7 @@ expect(page).to have_content('Hello there Fred') end - it "can mount a component defined in mounts code block" do + it "can mount a component defined in the mounts code block" do mount 'ShowOff' do class ShowOff include Hyperstack::Component @@ -88,8 +88,7 @@ def wait(seconds) it 'evaluate_promise will wait for the promise to resolve' do start = Time.now - answer = - evaluate_promise do + answer = evaluate_promise do wait(DELAY) end expect(answer).to eq(DELAY) @@ -157,7 +156,7 @@ def increment_click end - it "can add classes during testing" do + it "can add style classes during testing" do add_class :some_class, borderStyle: :solid mount 'StyledDiv' do class StyledDiv @@ -227,6 +226,59 @@ class StyledDiv expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(1).of(Time.now.to_i+@sync_gap) end end + + context "new style rspec expressions" do + + before(:each) do + @str = 'hello' + end + + let(:another_string) { 'a man a plan a canal panama' } + + it 'can evaluate expressions on the client using the on_client_to method' do + expect { 12 + 12 }.on_client_to eq 24 + end + + it 'with the on_client_to on a new line' do + expect do + 12 + 12 + end + .on_client_to eq 24 + end + + it 'can evaluate expressions on the client using the on_client_not_to method' do + expect { 12 + 12 }.on_client_not_to eq 25 + end + + it 'can use the to_then method to evaluate promises on the client' do + expect do + Promise.new.tap { |p| after(1) { p.resolve('done') } } + end.to_then eq('done') + end + + it 'will copy local vars to the client' do + str = 'hello' + expect { str.reverse }.on_client_to eq str.reverse + end + + it 'will copy instance vars to the client' do + expect { @str.reverse }.on_client_to eq @str.reverse + end + + it 'will copy memoized values to the client' do + expect { another_string.gsub(/\W/, '') }.on_client_to eq another_string.gsub(/\W/, '') + end + + it 'aliases evaluate_ruby as on_client and c?' do + expect(on_client { 12 % 5 }).to eq(2) + expect(c? { 12 % 5 }).to eq(2) + end + + it 'allows local variables on the client to be set using the with method' do + expect { with_var * 2 }.with(with_var: 4).on_client_to eq(8) + end + + end end RSpec::Steps.steps "will size_window to", js: true do diff --git a/ruby/hyper-spec/spec/test_app/app/views/components.rb b/ruby/hyper-spec/spec/test_app/app/views/components.rb index ed15dfb85..d81c614ca 100644 --- a/ruby/hyper-spec/spec/test_app/app/views/components.rb +++ b/ruby/hyper-spec/spec/test_app/app/views/components.rb @@ -2,6 +2,7 @@ require 'promise' require 'hyper-component' require 'hyper-state' +require 'time' if Hyperstack::Component::IsomorphicHelpers.on_opal_client? require 'browser' require 'browser/delay' diff --git a/ruby/hyper-spec/spec/test_app/bin/bundle b/ruby/hyper-spec/spec/test_app/bin/bundle old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/spec/test_app/bin/rails b/ruby/hyper-spec/spec/test_app/bin/rails old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/spec/test_app/bin/rake b/ruby/hyper-spec/spec/test_app/bin/rake old mode 100755 new mode 100644 diff --git a/ruby/hyper-spec/spec/test_app/bin/setup b/ruby/hyper-spec/spec/test_app/bin/setup old mode 100755 new mode 100644 From 42dc59ce53dcc45ecbd1f9f4ea7ee4afe6c3262b Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 15 Jan 2021 17:56:49 -0500 Subject: [PATCH 051/307] added dummy component back into hyper-spec - needs to be removed eventually --- .../lib/hyper-spec/component_test_helpers.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index bfca0ecd6..0ec700a6e 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -471,16 +471,20 @@ def self.add_class(class_name, styles={}) } end end - # class React::Component::HyperTestDummy < React::Component::Base - # def render; end - # end + class Hyperstack::Internal::Component::TestDummy + include Hyperstack::Component + render {} + end #{@_hyperspec_private_client_code} #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} code opts[:code] = opal_compile(block_with_helpers) end @_hyperspec_private_client_code = nil - #component_name ||= 'React::Component::HyperTestDummy' + # TODO: TestDummy is here to initialize the Hyper stack, but it also means you can't use + # hyper-spec without hyper-component. Figure out a better way to do this, or configure it. + # perhaps just some config setting to include certain code on init if needed? + component_name ||= 'Hyperstack::Internal::Component::TestDummy' value = [component_name, params, @_hyperspec_private_html_block, opts] ComponentTestHelpers.cache_write(test_url, value) @_hyperspec_private_html_block = nil From 1b2276a44ce71dcc13fccc94de78fbd3c9d2b17e Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 09:22:17 -0500 Subject: [PATCH 052/307] updated brittle specs and compatibility with new hyper-spec --- .travis.yml | 10 ++++---- .../spec/client_features/component_spec.rb | 17 ++++++++++---- .../spec/client_features/dsl_spec.rb | 8 +++++-- .../client_features/param_declaration_spec.rb | 16 ++++++------- .../param_declaration_legacy_spec.rb | 10 ++++---- ruby/hyper-component/spec/spec_helper.rb | 2 ++ ruby/hyper-i18n/spec/spec_helper.rb | 3 +++ ruby/hyper-model/spec/spec_helper.rb | 4 +++- ruby/hyper-operation/spec/spec_helper.rb | 3 +++ .../spec/hyper-store/reset_context_spec.rb | 2 +- upgrade-from-opal-0.11-rails-5.md | 23 +++++++++++++++++++ 11 files changed, 71 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3e930dd6d..98fe5a4f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,9 +80,9 @@ jobs: env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 - <<: *_test_gem env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' # - <<: *_test_gem # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' # - <<: *_test_gem @@ -108,8 +108,8 @@ jobs: # - <<: *_test_gem # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' # - <<: *_test_gem # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' # - <<: *_test_gem diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index a3725860e..836501a07 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -465,7 +465,7 @@ class Lorem; end describe 'Default props' do it 'sets default props using validation helper' do - on_client do + before_mount do class Foo include Hyperstack::Component params do @@ -474,14 +474,14 @@ class Foo end render do - DIV { @Foo + '-' + @Bar} + DIV(class: :foo) { @Foo + '-' + @Bar} end end end mount 'Foo' - expect(page.body[-40..-19]).to include("
foo-bar
") + expect(find('div.foo')).to have_content('foo-bar') mount 'Foo', foo: 'lorem' - expect(page.body[-40..-19]).to include("
lorem-bar
") + expect(find('div.foo')).to have_content('lorem-bar') end end end @@ -581,7 +581,14 @@ class Foo end end end - expect(page.body).to include('
') + expect( + JSON.parse( + find( + 'div[data-react-class="Hyperstack.Internal.Component.TopLevelRailsComponent"]', + visible: false + )['data-react-props'] + ).with_indifferent_access + ).to include render_params: {}, component_name: 'Foo', controller: 'HyperSpecTest' end end diff --git a/ruby/hyper-component/spec/client_features/dsl_spec.rb b/ruby/hyper-component/spec/client_features/dsl_spec.rb index 5903205aa..590a38d34 100644 --- a/ruby/hyper-component/spec/client_features/dsl_spec.rb +++ b/ruby/hyper-component/spec/client_features/dsl_spec.rb @@ -101,11 +101,15 @@ class Foo class Foo include Hyperstack::Component render do - DIV { "hello".br } + DIV(class: :foo) { "hello".br } end end end - expect(page.body[-285..-233]).to match(/(
hello<(br|br\/|br \/)><\/span><\/div>/) + expect( + find( + 'div[data-react-class="Hyperstack.Internal.Component.TopLevelRailsComponent"] div.foo span' + )['innerHTML'] + ).to eq 'hello
' end it "has a .td short hand String method" do diff --git a/ruby/hyper-component/spec/client_features/param_declaration_spec.rb b/ruby/hyper-component/spec/client_features/param_declaration_spec.rb index 2df289b69..40befc90b 100644 --- a/ruby/hyper-component/spec/client_features/param_declaration_spec.rb +++ b/ruby/hyper-component/spec/client_features/param_declaration_spec.rb @@ -7,11 +7,11 @@ class Foo < Hyperloop::Component collect_other_params_as :foo render do - DIV { @Foo[:bar] } + DIV(class: :foo) { @Foo[:bar] } end end end - expect(page.body[-35..-19]).to include("
biz
") + expect(find('div.foo')).to have_content 'biz' end it 'can override PropsWrapper.instance_var_name' do @@ -28,11 +28,11 @@ class Foo < Hyperloop::Component collect_other_params_as :foo render do - DIV { @foo[:bar] } + DIV(class: :foo) { @foo[:bar] } end end end - expect(page.body[-35..-19]).to include("
biz
") + expect(find('div.foo')).to have_content 'biz' end it 'defines collect_other_params_as method on params proxy' do @@ -66,11 +66,11 @@ class Foo < Hyperloop::Component param :foo render do - DIV { @Foo } + DIV(class: :foo) { @Foo } end end end - expect(page.body[-35..-19]).to include("
bar
") + expect(find('div.foo')).to have_content 'bar' end it "can give a param an accessor alias" do @@ -79,11 +79,11 @@ class Foo < Hyperloop::Component param :foo, alias: :bar render do - DIV { @bar } + DIV(class: :foo) { @bar } end end end - expect(page.body[-35..-19]).to include("
bar
") + expect(find('div.foo')).to have_content 'bar' end it "can create and access an optional params" do diff --git a/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb b/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb index b80ca8392..a7156e80b 100644 --- a/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb +++ b/ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb @@ -102,7 +102,7 @@ class Foo end end end - + it 'defines collect_other_params_as method on params proxy' do mount 'Foo', bar: 'biz' do class Foo < Hyperloop::Component @@ -110,11 +110,11 @@ class Foo < Hyperloop::Component collect_other_params_as :foo render do - DIV { params.foo[:bar] } + DIV(class: :foo) { params.foo[:bar] } end end end - expect(page.body[-35..-19]).to include("
biz
") + expect(find('div.foo')).to have_content 'biz' end it 'defines collect_other_params_as method on params proxy' do @@ -151,11 +151,11 @@ class Foo < Hyperloop::Component param :foo render do - DIV { params.foo } + DIV(class: :foo) { params.foo } end end end - expect(page.body[-35..-19]).to include("
bar
") + expect(find('div.foo')).to have_content 'bar' end it "can create and access an optional params" do diff --git a/ruby/hyper-component/spec/spec_helper.rb b/ruby/hyper-component/spec/spec_helper.rb index 8ba43b259..c90ce845a 100644 --- a/ruby/hyper-component/spec/spec_helper.rb +++ b/ruby/hyper-component/spec/spec_helper.rb @@ -52,6 +52,8 @@ class JavaScriptError < StandardError; end raise JavaScriptError, errors.join("\n\n") if errors.present? end end + + HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount end # Stubbing the React calls so we can test outside of Opal diff --git a/ruby/hyper-i18n/spec/spec_helper.rb b/ruby/hyper-i18n/spec/spec_helper.rb index 9b512f7fa..9927231b8 100644 --- a/ruby/hyper-i18n/spec/spec_helper.rb +++ b/ruby/hyper-i18n/spec/spec_helper.rb @@ -14,4 +14,7 @@ config.before(:all) do `rm -rf spec/test_app/tmp/cache/` end + + # Use legacy hyper-spec on_client behavior + HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount end diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index 11fd2acd5..378895490 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -25,7 +25,7 @@ def self.log_import(s) end end end - + config.color = true config.fail_fast = ENV['FAIL_FAST'] || false config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures") @@ -318,6 +318,8 @@ class JavaScriptError < StandardError; end # Capybara::Selenium::Driver.new(app, :browser => :chrome, :desired_capabilities => caps) # end + # Use legacy hyper-spec on_client behavior + HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount end FactoryBot.define do diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index 3fe0c454c..e2260d48d 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -131,6 +131,9 @@ def self.on_server? # #.reject { |e| e =~ /Unexpected response code: 200/ } # raise JavaScriptError, errors.join("\n\n") if errors.present? # end + + # Use legacy hyper-spec on_client behavior + HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount end module HyperSpec diff --git a/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb b/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb index 76cd93b84..8161d2dd0 100644 --- a/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb +++ b/ruby/hyper-store/spec/hyper-store/reset_context_spec.rb @@ -3,7 +3,7 @@ describe "resetting contexts" do it "does not reset any predefined boot receivers", js: true do - on_client do + before_mount do class Store include Hyperstack::Legacy::Store class << self diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index c0e008124..5a7e86400 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -259,3 +259,26 @@ ActiveRecord::Base. But in order for it to work the "page" was being passed so a page.evaluate_ruby, but the whole method was backwards. It should be a capybara helper method that takes a active record model. This is fixed. --- + +### Hyperspec changes behavior of on_client + +New hyperspec aliases evaluate_ruby as on_client for readability, the old on_client is now called before_mount. + +You can either update calls to on_client to be before_mount, or + +You can get legacy behavior by executing this line in the spec_helper: +```ruby +HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount +``` + +--- + +### HyperSpec todos + +TODO: Hyperspec is dependent on HyperComponent for mounting components, and initializing the system. + +However we want to make sure you can use HyperSpec without HyperComponent + +TODO: Investigate why we need before_mount... + +--- From 504a2b52920dd32983b7719e6c38daec81490df2 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 10:56:12 -0500 Subject: [PATCH 053/307] removed add_locals method --- .../spec/batch1/column_types/column_type_spec.rb | 2 +- ruby/hyper-model/spec/spec_helper.rb | 12 ++++++++++-- ruby/hyper-operation/spec/spec_helper.rb | 13 +++++++++++-- upgrade-from-opal-0.11-rails-5.md | 4 +++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb index b98181124..cb7e344a5 100644 --- a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb +++ b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb @@ -184,7 +184,7 @@ class DefaultTest < ActiveRecord::Base it 'loads and converts the value' do # randomly generates an error, but the exactual spec passed... perhaps move it up or down? (tried moving down one step) t = Timex.parse('1/2/2003') - r = TypeTest.create( + TypeTest.create( boolean: true, date: t.time, datetime: t.time, diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index 378895490..3ea6ca7d0 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -317,9 +317,17 @@ class JavaScriptError < StandardError; end # caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => [ "--window-size=200,200" ]}) # Capybara::Selenium::Driver.new(app, :browser => :chrome, :desired_capabilities => caps) # end +end - # Use legacy hyper-spec on_client behavior - HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount +module HyperSpec + module ComponentTestHelpers + # Use legacy hyper-spec on_client behavior + # turn off add_locals + alias_method :on_client, :before_mount + def add_locals(in_str, block) + in_str + end + end end FactoryBot.define do diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index e2260d48d..6b49cae30 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -131,11 +131,20 @@ def self.on_server? # #.reject { |e| e =~ /Unexpected response code: 200/ } # raise JavaScriptError, errors.join("\n\n") if errors.present? # end +end - # Use legacy hyper-spec on_client behavior - HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount +module HyperSpec + module ComponentTestHelpers + # Use legacy hyper-spec on_client behavior + # turn off add_locals + alias_method :on_client, :before_mount + def add_locals(in_str, block) + in_str + end + end end + module HyperSpec module ComponentTestHelpers alias old_expect_promise expect_promise diff --git a/upgrade-from-opal-0.11-rails-5.md b/upgrade-from-opal-0.11-rails-5.md index 5a7e86400..5258f2338 100644 --- a/upgrade-from-opal-0.11-rails-5.md +++ b/upgrade-from-opal-0.11-rails-5.md @@ -270,7 +270,6 @@ You can get legacy behavior by executing this line in the spec_helper: ```ruby HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount ``` - --- ### HyperSpec todos @@ -281,4 +280,7 @@ However we want to make sure you can use HyperSpec without HyperComponent TODO: Investigate why we need before_mount... +TODO: add_locals method only works on simple cases. For now make it experimental (i.e. make the method called + experimental_add_locals, which you can alias, or include a module that switches on the behavior.) + --- From a1ea8be623fca6217e03dd18744d4c3c5ff9730e Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 16:46:23 -0500 Subject: [PATCH 054/307] few more fixes to the new hyper-spec --- ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 0ec700a6e..26a985c17 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -271,7 +271,8 @@ def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) str = "#{name} = #{value.to_opal_expression}\n#{str}" end str = add_opal_block(str, block) if block - js = opal_compile(str).delete("\n").gsub('(Opal);', '(Opal)') + js = opal_compile(str).gsub("// Prepare super implicit arguments\n", '') + .delete("\n").gsub('(Opal);', '(Opal)') # workaround for firefox 58 and geckodriver 0.19.1, because firefox is unable to find .$to_json: # JSON.parse(evaluate_script("(function(){var a=Opal.Array.$new(); a[0]=#{js}; return a.$to_json();})();"), opts).first JSON.parse(evaluate_script("[#{js}].$to_json()"), opts).first @@ -533,7 +534,7 @@ def add_class(class_name, style) end def attributes_on_client(model) - evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes", symbolize_names: true) + evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys end From de69d8f0201aad36aa541f470ba1fc5b4917d8d1 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 21:19:03 -0500 Subject: [PATCH 055/307] rewrote hyper-spec add_locals method --- ruby/hyper-model/lib/hyper-model.rb | 1 + ruby/hyper-model/spec/spec_helper.rb | 12 +-- ruby/hyper-operation/spec/spec_helper.rb | 13 +-- .../lib/hyper-spec/component_test_helpers.rb | 90 ++++++++++++------- .../lib/sources/top_level_rails_component.rb | 7 ++ .../spec/test_app/app/views/components.rb | 1 + 6 files changed, 73 insertions(+), 51 deletions(-) diff --git a/ruby/hyper-model/lib/hyper-model.rb b/ruby/hyper-model/lib/hyper-model.rb index 93b5e51d0..767ec320c 100644 --- a/ruby/hyper-model/lib/hyper-model.rb +++ b/ruby/hyper-model/lib/hyper-model.rb @@ -59,6 +59,7 @@ require "reactive_record/active_record/public_columns_hash" require "reactive_record/serializers" require "reactive_record/pry" + require "hyper_spec/component_test_helpers" require_relative 'active_record_base' require 'hyper_model/version' diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index 3ea6ca7d0..378895490 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -317,17 +317,9 @@ class JavaScriptError < StandardError; end # caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => [ "--window-size=200,200" ]}) # Capybara::Selenium::Driver.new(app, :browser => :chrome, :desired_capabilities => caps) # end -end -module HyperSpec - module ComponentTestHelpers - # Use legacy hyper-spec on_client behavior - # turn off add_locals - alias_method :on_client, :before_mount - def add_locals(in_str, block) - in_str - end - end + # Use legacy hyper-spec on_client behavior + HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount end FactoryBot.define do diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index 6b49cae30..e2260d48d 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -131,20 +131,11 @@ def self.on_server? # #.reject { |e| e =~ /Unexpected response code: 200/ } # raise JavaScriptError, errors.join("\n\n") if errors.present? # end -end -module HyperSpec - module ComponentTestHelpers - # Use legacy hyper-spec on_client behavior - # turn off add_locals - alias_method :on_client, :before_mount - def add_locals(in_str, block) - in_str - end - end + # Use legacy hyper-spec on_client behavior + HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount end - module HyperSpec module ComponentTestHelpers alias old_expect_promise expect_promise diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 26a985c17..83e7cb563 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -33,24 +33,48 @@ def lines_for(file_name, name = nil) end class Object - def to_opal_expression - to_json - rescue Exception - to_s + def opal_serialize + nil end end -class Time - def to_opal_expression - "Time.parse('#{inspect}')" +class Hash + def opal_serialize + "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}"}.join(', ')}}" + end +end + +class Array + def opal_serialize + "[#{collect { |v| v.opal_serialize }.join(', ')}]" end end -class NilClass +[FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end +end + +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') + [Bignum, Fixnum].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end + end +end + +class Time def to_opal_expression - self.inspect + "Time.parse('#{inspect}')" end end +# +# class NilClass +# def to_opal_expression +# self.inspect +# end +# end module HyperSpec @@ -246,6 +270,11 @@ def isomorphic(&block) before_mount(&block) end + def set_local_var(name, object) + serialized = object.opal_serialize + "#{name} = #{serialized}" if serialized + end + def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) insure_page_loaded # TODO: better error message here...either you give us a block @@ -268,7 +297,8 @@ def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) end args.each do |name, value| - str = "#{name} = #{value.to_opal_expression}\n#{str}" + str = "#{set_local_var(name, value)}\n#{str}" + # str = "#{name} = #{value.to_opal_expression}\n#{str}" end str = add_opal_block(str, block) if block js = opal_compile(str).gsub("// Prepare super implicit arguments\n", '') @@ -319,19 +349,21 @@ def add_locals(in_str, block) memoized = b.eval('__memoized').instance_variable_get(:@memoized) in_str = memoized.inject(in_str) do |str, pair| - "#{str}\n#{pair.first} = #{pair.last.to_opal_expression}" + str = "#{str}\n#{set_local_var(pair.first, pair.last)}" + # "#{str}\n#{pair.first} = #{pair.last.to_opal_expression}" end if memoized in_str = b.local_variables.inject(in_str) do |str, var| next str if PRIVATE_VARIABLES.include? var - - "#{str}\n#{var} = #{b.local_variable_get(var).to_opal_expression}" + str = "#{str}\n#{set_local_var(var, b.local_variable_get(var))}" + # "#{str}\n#{var} = #{b.local_variable_get(var).to_opal_expression}" end in_str = b.eval('instance_variables').inject(in_str) do |str, var| next str if PRIVATE_VARIABLES.include? var - "#{str}\n#{var} = #{b.eval("instance_variable_get('#{var}')").to_opal_expression}" + str = "#{str}\n#{set_local_var(var, b.eval("instance_variable_get('#{var}')"))}" + # "#{str}\n#{var} = #{b.eval("instance_variable_get('#{var}')").to_opal_expression}" end in_str end @@ -442,6 +474,14 @@ def mount(component_name = nil, params = nil, opts = {}, &block) opts = client_options opts test_url = build_test_url_for(opts.delete(:controller)) if block || @_hyperspec_private_client_code || component_name.nil? + if defined? ::Hyperstack::Component + test_dummy = <<-code + class Hyperstack::Internal::Component::TestDummy + include Hyperstack::Component + render {} + end + code + end block_with_helpers = <<-code module ComponentHelpers def self.js_eval(s) @@ -472,25 +512,19 @@ def self.add_class(class_name, styles={}) } end end - class Hyperstack::Internal::Component::TestDummy - include Hyperstack::Component - render {} - end + #{test_dummy} #{@_hyperspec_private_client_code} #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} code opts[:code] = opal_compile(block_with_helpers) end @_hyperspec_private_client_code = nil - # TODO: TestDummy is here to initialize the Hyper stack, but it also means you can't use - # hyper-spec without hyper-component. Figure out a better way to do this, or configure it. - # perhaps just some config setting to include certain code on init if needed? - component_name ||= 'Hyperstack::Internal::Component::TestDummy' + component_name ||= 'Hyperstack::Internal::Component::TestDummy' if defined? ::Hyperstack::Component value = [component_name, params, @_hyperspec_private_html_block, opts] ComponentTestHelpers.cache_write(test_url, value) @_hyperspec_private_html_block = nil test_code_key = "hyper_spec_prerender_test_code.js" - if defined? ::Hyperstack::Component # was ::React in old hyperloop version + if defined? ::Hyperstack::Component @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] if opts[:render_on] == :both || opts[:render_on] == :server_only unless opts[:code].blank? @@ -533,11 +567,6 @@ def add_class(class_name, style) @_hyperspec_private_client_code = "#{@_hyperspec_private_client_code}ComponentHelpers.add_class('#{class_name}', #{style})\n" end - def attributes_on_client(model) - evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys - end - - def open_in_chrome if false && ['linux', 'freebsd'].include?(`uname`.downcase) `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}` @@ -689,8 +718,9 @@ def initialize(target, args) @target = target @args_str = args.collect do |name, value| - "#{name} = #{value.to_opal_expression}" - end.join("\n") + set_local_var(name, value) + # "#{name} = #{value.to_opal_expression}" + end.compact.join("\n") end end diff --git a/ruby/hyper-spec/lib/sources/top_level_rails_component.rb b/ruby/hyper-spec/lib/sources/top_level_rails_component.rb index ae655c080..d7f2451b1 100644 --- a/ruby/hyper-spec/lib/sources/top_level_rails_component.rb +++ b/ruby/hyper-spec/lib/sources/top_level_rails_component.rb @@ -1,3 +1,10 @@ +class Time + def self._load(*args) + debugger + nil + end +end + module Hyperstack module Internal module Component diff --git a/ruby/hyper-spec/spec/test_app/app/views/components.rb b/ruby/hyper-spec/spec/test_app/app/views/components.rb index d81c614ca..b205e4276 100644 --- a/ruby/hyper-spec/spec/test_app/app/views/components.rb +++ b/ruby/hyper-spec/spec/test_app/app/views/components.rb @@ -3,6 +3,7 @@ require 'hyper-component' require 'hyper-state' require 'time' +require 'opal/full' if Hyperstack::Component::IsomorphicHelpers.on_opal_client? require 'browser' require 'browser/delay' From 2c4de175047030a198673658fb3456c513b0241b Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 21:44:46 -0500 Subject: [PATCH 056/307] regressions fixed --- ruby/hyper-model/lib/hyper-model.rb | 1 - ruby/hyper-spec/lib/sources/top_level_rails_component.rb | 7 ------- ruby/hyper-spec/spec/test_app/app/views/components.rb | 1 - 3 files changed, 9 deletions(-) diff --git a/ruby/hyper-model/lib/hyper-model.rb b/ruby/hyper-model/lib/hyper-model.rb index 767ec320c..93b5e51d0 100644 --- a/ruby/hyper-model/lib/hyper-model.rb +++ b/ruby/hyper-model/lib/hyper-model.rb @@ -59,7 +59,6 @@ require "reactive_record/active_record/public_columns_hash" require "reactive_record/serializers" require "reactive_record/pry" - require "hyper_spec/component_test_helpers" require_relative 'active_record_base' require 'hyper_model/version' diff --git a/ruby/hyper-spec/lib/sources/top_level_rails_component.rb b/ruby/hyper-spec/lib/sources/top_level_rails_component.rb index d7f2451b1..ae655c080 100644 --- a/ruby/hyper-spec/lib/sources/top_level_rails_component.rb +++ b/ruby/hyper-spec/lib/sources/top_level_rails_component.rb @@ -1,10 +1,3 @@ -class Time - def self._load(*args) - debugger - nil - end -end - module Hyperstack module Internal module Component diff --git a/ruby/hyper-spec/spec/test_app/app/views/components.rb b/ruby/hyper-spec/spec/test_app/app/views/components.rb index b205e4276..d81c614ca 100644 --- a/ruby/hyper-spec/spec/test_app/app/views/components.rb +++ b/ruby/hyper-spec/spec/test_app/app/views/components.rb @@ -3,7 +3,6 @@ require 'hyper-component' require 'hyper-state' require 'time' -require 'opal/full' if Hyperstack::Component::IsomorphicHelpers.on_opal_client? require 'browser' require 'browser/delay' From 4fb868d46b3f8e691d2fe1e567c5d9417a55c95f Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 22:15:32 -0500 Subject: [PATCH 057/307] moved attributes_on_client to hyper-model --- ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb diff --git a/ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb b/ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb new file mode 100644 index 000000000..7164f69d9 --- /dev/null +++ b/ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb @@ -0,0 +1,7 @@ +module HyperSpec + module ComponentTestHelpers + def attributes_on_client(model) + evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys + end + end +end From 46f0bb887e551b7f8d58315e4390233f479eb572 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 22:38:55 -0500 Subject: [PATCH 058/307] moved attributes_on_client back to hyper-spec --- ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb | 7 ------- ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb | 4 ++++ 2 files changed, 4 insertions(+), 7 deletions(-) delete mode 100644 ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb diff --git a/ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb b/ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb deleted file mode 100644 index 7164f69d9..000000000 --- a/ruby/hyper-model/lib/hyper_spec/component_test_helpers.rb +++ /dev/null @@ -1,7 +0,0 @@ -module HyperSpec - module ComponentTestHelpers - def attributes_on_client(model) - evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys - end - end -end diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 83e7cb563..d429cebb4 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -647,6 +647,10 @@ def size_window(width = nil, height = nil) rescue StandardError true end + + def attributes_on_client(model) + evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys + end end RSpec.configure do |config| From 419191b2825839c2e49854159f507d33873e8b76 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 16 Jan 2021 22:40:57 -0500 Subject: [PATCH 059/307] run all specs --- .travis.yml | 98 ++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98fe5a4f4..9927bc5af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,57 +83,57 @@ jobs: - <<: *_test_gem env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - <<: *_deploy_gem env: COMPONENT=hyper-i18n From 52755b7609d0b5288935c2b8f2383c406ed3c0b5 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 17 Jan 2021 15:04:35 -0500 Subject: [PATCH 060/307] fixed intermittent failure --- .../spec/batch1/column_types/column_type_spec.rb | 6 ++++-- ruby/rails-hyperstack/.gitignore | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb index cb7e344a5..c6ad92680 100644 --- a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb +++ b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb @@ -149,8 +149,10 @@ class DefaultTest < ActiveRecord::Base TypeTest.serialize :string TypeTest.serialize :text end - [:string, :text].each do |attr| - expect_evaluate_ruby("TypeTest.find(1).#{attr}.class").to eq('NilClass') + %i[string text].each_with_index do |attr, i| + # find a different record for each iteration to prevent finding a model + # which is loaded + expect { TypeTest.find(i + 1)[attr].class }.on_client_to eq('NilClass') end end diff --git a/ruby/rails-hyperstack/.gitignore b/ruby/rails-hyperstack/.gitignore index 9abccdc3d..71aa55cc3 100644 --- a/ruby/rails-hyperstack/.gitignore +++ b/ruby/rails-hyperstack/.gitignore @@ -50,3 +50,8 @@ bower.json # ignore Gemfile.locks https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/ /spec/test_app/Gemfile.lock /Gemfile.lock + +node_modules +package.json +spec/test_app +yarn.lock From 3dd24b97c2ef48a4ca0f43eb73a4a828fa6d6cc5 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 17 Jan 2021 17:52:16 -0500 Subject: [PATCH 061/307] better handling of unserialized vars in hyper-spec --- .../lib/hyper-spec/component_test_helpers.rb | 29 +++++++++---------- ruby/hyper-spec/spec/hyper_spec.rb | 21 ++++++++++++++ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index d429cebb4..c61fd4a4a 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -69,17 +69,10 @@ def to_opal_expression "Time.parse('#{inspect}')" end end -# -# class NilClass -# def to_opal_expression -# self.inspect -# end -# end module HyperSpec # add a before eval hook to pry so we can capture the source - if defined? Pry class << self attr_accessor :current_pry_code_block @@ -272,7 +265,14 @@ def isomorphic(&block) def set_local_var(name, object) serialized = object.opal_serialize - "#{name} = #{serialized}" if serialized + if serialized + "#{name} = #{serialized}" + else + "self.class.define_method(:#{name}) "\ + "{ raise 'Attempt to access the variable #{name} "\ + 'that was defined in the spec, but its value could not be serialized '\ + "so it is undefined on the client.' }" + end end def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) @@ -349,21 +349,19 @@ def add_locals(in_str, block) memoized = b.eval('__memoized').instance_variable_get(:@memoized) in_str = memoized.inject(in_str) do |str, pair| - str = "#{str}\n#{set_local_var(pair.first, pair.last)}" - # "#{str}\n#{pair.first} = #{pair.last.to_opal_expression}" + "#{str}\n#{set_local_var(pair.first, pair.last)}" end if memoized in_str = b.local_variables.inject(in_str) do |str, var| next str if PRIVATE_VARIABLES.include? var - str = "#{str}\n#{set_local_var(var, b.local_variable_get(var))}" - # "#{str}\n#{var} = #{b.local_variable_get(var).to_opal_expression}" + + "#{str}\n#{set_local_var(var, b.local_variable_get(var))}" end in_str = b.eval('instance_variables').inject(in_str) do |str, var| next str if PRIVATE_VARIABLES.include? var - str = "#{str}\n#{set_local_var(var, b.eval("instance_variable_get('#{var}')"))}" - # "#{str}\n#{var} = #{b.eval("instance_variable_get('#{var}')").to_opal_expression}" + "#{str}\n#{set_local_var(var, b.eval("instance_variable_get('#{var}')"))}" end in_str end @@ -723,8 +721,7 @@ def initialize(target, args) @target = target @args_str = args.collect do |name, value| set_local_var(name, value) - # "#{name} = #{value.to_opal_expression}" - end.compact.join("\n") + end.join("\n") end end diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 148836f84..5e4f54145 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -269,6 +269,27 @@ class StyledDiv expect { another_string.gsub(/\W/, '') }.on_client_to eq another_string.gsub(/\W/, '') end + it 'will deal with unserailized local vars, instance vars and memoized values correctly' do + foo_bar = page + expect do + evaluate_ruby { foo_bar } + end.to raise_error(Exception, /foo_bar/) + end + + it 'will ignore unserailized local vars, instance vars and memoized values if not accessed' do + foo_bar = page + good_value = 12 + expect { good_value * 2 }.on_client_to eq good_value * 2 + end + + it 'will allow unserailized local vars, instance vars and memoized values can be redefined on the client' do + foo_bar = page + expect do + foo_bar = 12 + foo_bar * 2 + end.on_client_to eq 24 + end + it 'aliases evaluate_ruby as on_client and c?' do expect(on_client { 12 % 5 }).to eq(2) expect(c? { 12 % 5 }).to eq(2) From 99f1543ed0dcebd229ba533d520bd3c80ce49ad8 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 20 Jan 2021 18:19:42 -0500 Subject: [PATCH 062/307] removed hard dependency on pry from hyper-spec --- ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb | 7 +++++-- ruby/hyper-spec/spec/hyper_spec.rb | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index c61fd4a4a..5e2e43985 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -3,7 +3,11 @@ require 'unparser' require 'hyper-spec/unparser_patch' # not present in original version of refactored hyperspec require 'method_source' -require 'pry' +begin + require 'pry' +rescue LoadError + nil +end require_relative '../../lib/hyper-spec/time_cop.rb' require 'filecache' @@ -298,7 +302,6 @@ def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) args.each do |name, value| str = "#{set_local_var(name, value)}\n#{str}" - # str = "#{name} = #{value.to_opal_expression}\n#{str}" end str = add_opal_block(str, block) if block js = opal_compile(str).gsub("// Prepare super implicit arguments\n", '') diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 5e4f54145..4361dba41 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -284,7 +284,7 @@ class StyledDiv it 'will allow unserailized local vars, instance vars and memoized values can be redefined on the client' do foo_bar = page - expect do + expect do foo_bar = 12 foo_bar * 2 end.on_client_to eq 24 @@ -298,7 +298,6 @@ class StyledDiv it 'allows local variables on the client to be set using the with method' do expect { with_var * 2 }.with(with_var: 4).on_client_to eq(8) end - end end From 4f2078e2c395121705536b2364075261b8bdb361 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 24 Jan 2021 12:00:03 -0500 Subject: [PATCH 063/307] initial refactor --- .../lib/hyper-spec/component_test_helpers.rb | 219 ++++++++++-------- 1 file changed, 124 insertions(+), 95 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 5e2e43985..0f6aed99d 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -86,6 +86,101 @@ class << self end end + module ControllerHelpers + TOP_LEVEL_COMPONENT_PATCH = + Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) + TIME_COP_CLIENT_PATCH = + Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + + "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" + + def initialize! + head(:no_content) && return if params[:id] == 'ping' + route_root = self.class.name.gsub(/Controller$/, '').underscore + key = "/#{route_root}/#{params[:id]}" + test_params = ComponentTestHelpers.cache_read(key) + @component_name = test_params[0] + @component_params = test_params[1] + @html_block = test_params[2] + @render_params = test_params[3] + @render_on = @render_params.delete(:render_on) || :client_only + @_mock_time = @render_params.delete(:mock_time) + @style_sheet = @render_params.delete(:style_sheet) + @javascript = @render_params.delete(:javascript) + @code = @render_params.delete(:code) + + @page = "\n" + end + + def mount_component! + @page = '<%= react_component @component_name, @component_params, '\ + "{ prerender: #{@render_on != :client_only} } %>\n#{@page}" + end + + def client_code! + if @component_name + @page = "\n#{@page}" + end + @page = "\n#{@page}" if @code + end + + def time_cop_patch! + @page = "\n#{@page}" + end + + def application! + @page = "<%= javascript_include_tag '#{@javascript || 'application'}' %>\n#{@page}" + end + + def style_sheet! + @page = "<%= stylesheet_link_tag '#{@style_sheet || 'application'}' %>\n#{@page}" + end + + def go_function! + @page = "\n#{@page}" + end + + def client_title! + title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) + title = "#{title}...continued." if ComponentTestHelpers.description_displayed + + @page = "\n#{@page}" + + ComponentTestHelpers.description_displayed = true + end + + def html_block! + @page = "\n#{@html_block}\n#{@page}" + end + + def deliver! + @render_params[:inline] = @page + response.headers['Cache-Control'] = 'max-age=120' + response.headers['X-Tracking-ID'] = '123456' + render @render_params + end + + def server_only? + @render_on == :server_only + end + + def test + initialize! + # TODO: reverse the the layout in the above methods so they can run in + # the right order + mount_component! if @component_name + client_code! unless server_only? + time_cop_patch! if !server_only? || Lolex.initialized? + application! if (!server_only? && !@render_params[:layout]) || @javascript + style_sheet! if !@render_params[:layout] || @style_sheet + go_function! + client_title! if ComponentTestHelpers.current_example + html_block! + deliver! + end + end + module ComponentTestHelpers def self.opal_compile(s) Opal.compile(s) @@ -98,13 +193,6 @@ def opal_compile(s) ComponentTestHelpers.opal_compile(s) end - TOP_LEVEL_COMPONENT_PATCH = - Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) - TIME_COP_CLIENT_PATCH = - Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + - "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" - - class << self attr_accessor :current_example attr_accessor :description_displayed @@ -160,102 +248,43 @@ def cache_delete(key) # to ActionController::Base def new_controller - if defined? ApplicationController - Class.new ApplicationController - else - Class.new ::ActionController::Base - end - end + return ::HyperSpecTestController if defined?(::HyperSpecTestController) - def build_test_url_for(controller, ping = nil) - unless controller - unless defined?(::HyperSpecTestController) - Object.const_set('HyperSpecTestController', new_controller) - end + controller = if defined? ApplicationController + Class.new ApplicationController + elsif defined? ::ActionController::Base + Class.new ::ActionController::Base + end - controller = ::HyperSpecTestController - end - - route_root = controller.name.gsub(/Controller$/, '').underscore + raise raise 'No HyperSpecTestController class found' unless controller - unless controller.method_defined?(:test) - controller.class_eval do - define_method(:test) do - head(:no_content) && return if params[:id] == 'ping' - route_root = self.class.name.gsub(/Controller$/, '').underscore - key = "/#{route_root}/#{params[:id]}" - test_params = ComponentTestHelpers.cache_read(key) - @component_name = test_params[0] - @component_params = test_params[1] - html_block = test_params[2] - render_params = test_params[3] - render_on = render_params.delete(:render_on) || :client_only - _mock_time = render_params.delete(:mock_time) - style_sheet = render_params.delete(:style_sheet) - javascript = render_params.delete(:javascript) - code = render_params.delete(:code) - #page = "#{html_block}\n\n" - page = "\n" - - page = '<%= react_component @component_name, @component_params, '\ - "{ prerender: #{render_on != :client_only} } %>\n#{page}" if @component_name - #page = "\n#{page}" - unless render_on == :server_only - page = "\n#{page}" if @component_name - page = "\n#{page}" if code - end - - if render_on != :server_only || Lolex.initialized? - page = "\n#{page}" - end - - if (render_on != :server_only && !render_params[:layout]) || javascript - page = "<%= javascript_include_tag '#{javascript || 'application'}' %>\n#{page}" - end + Object.const_set('HyperSpecTestController', controller) + end - if !render_params[:layout] || style_sheet - page = "<%= stylesheet_link_tag '#{style_sheet || 'application'}' %>\n#{page}" - end - page = "\n#{page}" + def add_rails_route(route_root) + routes = ::Rails.application.routes + routes.disable_clear_and_finalize = true + routes.clear! + routes.draw { get "/#{route_root}/:id", to: "#{route_root}#test" } + ::Rails.application.routes_reloader.paths.each { |path| load(path) } + routes.finalize! + ActiveSupport.on_load(:action_controller) { routes.finalize! } + ensure + routes.disable_clear_and_finalize = false + end - if ComponentTestHelpers.current_example + def build_test_url_for(controller = nil, ping = nil) + controller ||= new_controller + route_root = controller.name.gsub(/Controller$/, '').underscore - title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) - title = "#{title}...continued." if ComponentTestHelpers.description_displayed + unless controller.method_defined?(:test) + controller.include ControllerHelpers + add_rails_route(route_root) + end - page = "\n#{page}" + id = ping ? 'ping' : ComponentTestHelpers.test_id - ComponentTestHelpers.description_displayed = true - end - page = "\n#{html_block}\n#{page}" - render_params[:inline] = page - response.headers['Cache-Control'] = 'max-age=120' - response.headers['X-Tracking-ID'] = '123456' - render render_params - end - end - - begin - routes = ::Rails.application.routes - routes.disable_clear_and_finalize = true - routes.clear! - routes.draw do - get "/#{route_root}/:id", to: "#{route_root}#test" - end - ::Rails.application.routes_reloader.paths.each { |path| load(path) } - routes.finalize! - ActiveSupport.on_load(:action_controller) { routes.finalize! } - ensure - routes.disable_clear_and_finalize = false - end - end - if ping - "/#{route_root}/ping" - else - "/#{route_root}/#{ComponentTestHelpers.test_id}" - end + "/#{route_root}/#{id}" end def insert_html(str) From d1a00ead5a939eda9e4e5e34ffa2809bb8b77337 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 24 Jan 2021 15:43:40 -0500 Subject: [PATCH 064/307] more refactors --- ruby/hyper-spec/hyper-spec.gemspec | 1 + ruby/hyper-spec/lib/hyper-spec.rb | 99 +++--- .../lib/hyper-spec/component_test_helpers.rb | 296 +----------------- .../lib/hyper-spec/controller_helpers.rb | 121 +++++++ .../hyper-spec/lib/hyper-spec/expectations.rb | 76 +++++ ruby/hyper-spec/lib/hyper-spec/patches.rb | 68 ++++ .../lib/hyper-spec/unparser_patch.rb | 10 - 7 files changed, 340 insertions(+), 331 deletions(-) create mode 100644 ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/expectations.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/patches.rb delete mode 100644 ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index d1af270f0..2018b1fc2 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] + spec.add_dependency 'actionview' spec.add_dependency 'capybara' spec.add_dependency 'chromedriver-helper', '1.2.0' spec.add_dependency 'filecache' diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 1f2c76353..9f7ed1ea0 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -1,13 +1,46 @@ -require 'capybara/rspec' +# hyper-spec +require 'action_view' require 'opal' -require 'selenium-webdriver' +require 'unparser' +require 'method_source' +require 'hyper-spec/time_cop.rb' +require 'filecache' +require 'capybara/rspec' require 'hyper-spec/component_test_helpers' +require 'hyper-spec/controller_helpers' +require 'hyper-spec/patches' require 'hyper-spec/version' require 'hyper-spec/wait_for_ajax' +require 'hyper-spec/expectations' +require 'parser/current' require 'selenium/web_driver/firefox/profile' +require 'selenium-webdriver' + +begin + require 'pry' +rescue LoadError + nil +end + +Parser::Builders::Default.emit_procarg0 = true + +# not available in parser 2.3 +if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 + Parser::Builders::Default.emit_arg_inside_procarg0 = true +end module HyperSpec + if defined? Pry + # add a before eval hook to pry so we can capture the source + class << self + attr_accessor :current_pry_code_block + Pry.hooks.add_hook(:before_eval, 'hyper_spec_code_capture') do |code| + HyperSpec.current_pry_code_block = code + end + end + end + def self.reset_between_examples=(value) RSpec.configuration.reset_between_examples = value end @@ -21,7 +54,8 @@ def self.reset_sessions! end end -# TODO: figure out why we need this patch - its because we are on an old version of Selenium Webdriver, but why? +# TODO: figure out why we need this patch - its because we are on an old version +# of Selenium Webdriver, but why? require 'selenium-webdriver' module Selenium @@ -33,7 +67,7 @@ module Bridge COMMANDS.freeze def log(type) - data = execute :get_log, {}, {type: type.to_s} + data = execute :get_log, {}, type: type.to_s Array(data).map do |l| begin @@ -48,10 +82,9 @@ def log(type) end end - module Capybara class << self - alias_method :old_reset_sessions!, :reset_sessions! + alias old_reset_sessions! reset_sessions! def reset_sessions! old_reset_sessions! if HyperSpec.reset_between_examples? end @@ -80,19 +113,22 @@ def reset_sessions! config.add_setting :debugger_width, default: nil - config.before(:each) do - Hyperstack.class_eval do - def self.on_server? - true + if defined?(Hyperstack) + Hyperstack.class_eval do + def self.on_server? + true + end end - end if defined?(Hyperstack) + end # for compatibility with HyperMesh - HyperMesh.class_eval do - def self.on_server? - true + if defined?(HyperMesh) + HyperMesh.class_eval do + def self.on_server? + true + end end - end if defined?(HyperMesh) + end end config.before(:each, js: true) do @@ -112,6 +148,11 @@ def self.on_server? # Capybara config RSpec.configure do |config| + config.before(:each) do |example| + HyperSpec::ComponentTestHelpers.current_example = example + HyperSpec::ComponentTestHelpers.description_displayed = false + end + config.add_setting :wait_for_initialization_time config.wait_for_initialization_time = 3 @@ -121,28 +162,15 @@ def self.on_server? options = {} options.merge!( w3c: false, - args: %w[auto-open-devtools-for-tabs]) #, - #prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - #) unless ENV['NO_DEBUGGER'] - # this does not seem to work properly. Don't document this feature yet. - # options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] + args: %w[auto-open-devtools-for-tabs] + ) options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options, 'goog:loggingPrefs' => {browser: 'ALL'}) + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: options, 'goog:loggingPrefs' => { browser: 'ALL' } + ) Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) end - # Capybara.register_driver :chrome do |app| - # options = {} - # options.merge!( - # args: %w[auto-open-devtools-for-tabs], - # prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - # ) unless ENV['NO_DEBUGGER'] - # # this does not seem to work properly. Don't document this feature yet. - # options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - # capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) - # Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) - # end - Capybara.register_driver :firefox do |app| Capybara::Selenium::Driver.new(app, browser: :firefox) end @@ -152,7 +180,9 @@ def self.on_server? options.add_argument('--headless') options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') - Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options) + Capybara::Selenium::Driver.new( + app, browser: :chrome, driver_path: '/usr/lib/chromium-browser/chromedriver', options: options + ) end Capybara.register_driver :firefox_headless do |app| @@ -184,5 +214,4 @@ def self.on_server? when 'travis' then :chrome_headless_docker_travis else :selenium_chrome_headless end - end diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 0f6aed99d..855a9035b 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -1,194 +1,15 @@ # see component_test_helpers_spec.rb for examples -require 'parser/current' -require 'unparser' -require 'hyper-spec/unparser_patch' # not present in original version of refactored hyperspec -require 'method_source' -begin - require 'pry' -rescue LoadError - nil -end -require_relative '../../lib/hyper-spec/time_cop.rb' -require 'filecache' - -Parser::Builders::Default.emit_procarg0 = true # not present in original version of refactored hyperspec -if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 - Parser::Builders::Default.emit_arg_inside_procarg0 = true # not available in parser 2.3 -end - -module MethodSource - class << self - alias original_lines_for_before_hyper_spec lines_for - alias original_source_helper_before_hyper_spec source_helper - - def source_helper(source_location, name=nil) - source_location[1] = 1 if source_location[0] == '(pry)' - original_source_helper_before_hyper_spec source_location, name - end - - def lines_for(file_name, name = nil) - if file_name == '(pry)' - HyperSpec.current_pry_code_block - else - original_lines_for_before_hyper_spec file_name, name - end - end - end -end - -class Object - def opal_serialize - nil - end -end - -class Hash - def opal_serialize - "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}"}.join(', ')}}" - end -end - -class Array - def opal_serialize - "[#{collect { |v| v.opal_serialize }.join(', ')}]" - end -end - -[FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass].each do |klass| - klass.send(:define_method, :opal_serialize) do - inspect - end -end - -if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') - [Bignum, Fixnum].each do |klass| - klass.send(:define_method, :opal_serialize) do - inspect - end - end -end - -class Time - def to_opal_expression - "Time.parse('#{inspect}')" - end -end - module HyperSpec - - # add a before eval hook to pry so we can capture the source - if defined? Pry - class << self - attr_accessor :current_pry_code_block - Pry.hooks.add_hook(:before_eval, "hyper_spec_code_capture") do |code| - HyperSpec.current_pry_code_block = code - end - end - end - - module ControllerHelpers - TOP_LEVEL_COMPONENT_PATCH = - Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) - TIME_COP_CLIENT_PATCH = - Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + - "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" - - def initialize! - head(:no_content) && return if params[:id] == 'ping' - route_root = self.class.name.gsub(/Controller$/, '').underscore - key = "/#{route_root}/#{params[:id]}" - test_params = ComponentTestHelpers.cache_read(key) - @component_name = test_params[0] - @component_params = test_params[1] - @html_block = test_params[2] - @render_params = test_params[3] - @render_on = @render_params.delete(:render_on) || :client_only - @_mock_time = @render_params.delete(:mock_time) - @style_sheet = @render_params.delete(:style_sheet) - @javascript = @render_params.delete(:javascript) - @code = @render_params.delete(:code) - - @page = "\n" - end - - def mount_component! - @page = '<%= react_component @component_name, @component_params, '\ - "{ prerender: #{@render_on != :client_only} } %>\n#{@page}" - end - - def client_code! - if @component_name - @page = "\n#{@page}" - end - @page = "\n#{@page}" if @code - end - - def time_cop_patch! - @page = "\n#{@page}" - end - - def application! - @page = "<%= javascript_include_tag '#{@javascript || 'application'}' %>\n#{@page}" - end - - def style_sheet! - @page = "<%= stylesheet_link_tag '#{@style_sheet || 'application'}' %>\n#{@page}" - end - - def go_function! - @page = "\n#{@page}" - end - - def client_title! - title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) - title = "#{title}...continued." if ComponentTestHelpers.description_displayed - - @page = "\n#{@page}" - - ComponentTestHelpers.description_displayed = true - end - - def html_block! - @page = "\n#{@html_block}\n#{@page}" - end - - def deliver! - @render_params[:inline] = @page - response.headers['Cache-Control'] = 'max-age=120' - response.headers['X-Tracking-ID'] = '123456' - render @render_params - end - - def server_only? - @render_on == :server_only - end - - def test - initialize! - # TODO: reverse the the layout in the above methods so they can run in - # the right order - mount_component! if @component_name - client_code! unless server_only? - time_cop_patch! if !server_only? || Lolex.initialized? - application! if (!server_only? && !@render_params[:layout]) || @javascript - style_sheet! if !@render_params[:layout] || @style_sheet - go_function! - client_title! if ComponentTestHelpers.current_example - html_block! - deliver! - end - end - module ComponentTestHelpers - def self.opal_compile(s) - Opal.compile(s) + def self.opal_compile(str) + Opal.compile(str) rescue Exception => e - puts "puts could not compile: \n\n#{s}\n\n" + puts "puts could not compile: \n\n#{str}\n\n" raise e end + extend ActionView::Helpers::JavaScriptHelper + def opal_compile(s) ComponentTestHelpers.opal_compile(s) end @@ -206,34 +27,21 @@ def display_example_description "" end - RAILS_CACHE = false def file_cache @file_cache ||= FileCache.new("cache", "/tmp/hyper-spec-caches", 30, 3) end def cache_read(key) - if RAILS_CACHE - ::Rails.cache.read(key) - else - file_cache.get(key) - end + file_cache.get(key) end def cache_write(key, value) - if RAILS_CACHE - ::Rails.cache.write(key, value) - else - file_cache.set(key, value) - end + file_cache.set(key, value) end def cache_delete(key) - if RAILS_CACHE - ::Rails.cache.write(key, value) - else - file_cache.delete(key) - end + file_cache.delete(key) rescue StandardError nil end @@ -275,16 +83,15 @@ def add_rails_route(route_root) def build_test_url_for(controller = nil, ping = nil) controller ||= new_controller - route_root = controller.name.gsub(/Controller$/, '').underscore unless controller.method_defined?(:test) controller.include ControllerHelpers - add_rails_route(route_root) + add_rails_route(controller.route_root) end id = ping ? 'ping' : ComponentTestHelpers.test_id - "/#{route_root}/#{id}" + "/#{controller.route_root}/#{id}" end def insert_html(str) @@ -682,87 +489,4 @@ def attributes_on_client(model) evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys end end - - RSpec.configure do |config| - config.before(:each) do |example| - ComponentTestHelpers.current_example = example - ComponentTestHelpers.description_displayed = false - end - end -end - -module RSpec - module Expectations - class ExpectationTarget - end - - module HyperSpecInstanceMethods - - def self.included(base) - base.include HyperSpec::ComponentTestHelpers - end - - def to_on_client(matcher, message = nil, &block) - evaluate_client('ruby').to(matcher, message, &block) - end - - alias on_client_to to_on_client - - def to_on_client_not(matcher, message = nil, &block) - evaluate_client('ruby').not_to(matcher, message, &block) - end - - alias on_client_to_not to_on_client_not - alias on_client_not_to to_on_client_not - alias to_not_on_client to_on_client_not - alias not_to_on_client to_on_client_not - - def to_then(matcher, message = nil, &block) - evaluate_client('promise').to(matcher, message, &block) - end - - alias then_to to_then - - def to_then_not(matcher, message = nil, &block) - evaluate_client('promise').not_to(matcher, message, &block) - end - - alias then_to_not to_then_not - alias then_not_to to_then_not - alias to_not_then to_then_not - alias not_to_then to_then_not - - private - - def evaluate_client(method) - source = add_opal_block(@args_str, @target) - value = @target.binding.eval("evaluate_#{method}(#{source.inspect}, {}, {})") - ExpectationTarget.for(value, nil) - end - end - - class OnClientWithArgsTarget - include HyperSpecInstanceMethods - - def initialize(target, args) - unless args.is_a? Hash - raise ExpectationNotMetError, - "You must pass a hash of local var, value pairs to the 'with' modifier" - end - - @target = target - @args_str = args.collect do |name, value| - set_local_var(name, value) - end.join("\n") - end - end - - class BlockExpectationTarget < ExpectationTarget - include HyperSpecInstanceMethods - - def with(args) - OnClientWithArgsTarget.new(@target, args) - end - end - end end diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb new file mode 100644 index 000000000..32d736f6d --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -0,0 +1,121 @@ +module HyperSpec + module ControllerHelpers + TOP_LEVEL_COMPONENT_PATCH = + Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) + TIME_COP_CLIENT_PATCH = + Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + + "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" + + def self.included(base) + def base.route_root + # Implement underscore without using rails underscore, so we don't have a + # dependency on ActiveSupport + name.gsub(/Controller$/, '') + .gsub(/::/, '/') + .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + .gsub(/([a-z\d])([A-Z])/, '\1_\2') + .tr('-', '_') + .downcase + end + end + + def initialize! + return ping! if params[:id] == 'ping' + + key = "/#{self.class.route_root}/#{params[:id]}" + test_params = ComponentTestHelpers.cache_read(key) + + @component_name = test_params[0] + @component_params = test_params[1] + @html_block = test_params[2] + @render_params = test_params[3] + @render_on = @render_params.delete(:render_on) || :client_only + @_mock_time = @render_params.delete(:mock_time) + @style_sheet = @render_params.delete(:style_sheet) + @javascript = @render_params.delete(:javascript) + @code = @render_params.delete(:code) + + @page = "\n" + end + + def ping! + head(:no_content) + nil + end + + def mount_component! + @page = '<%= react_component @component_name, @component_params, '\ + "{ prerender: #{@render_on != :client_only} } %>\n#{@page}" + end + + def client_code! + if @component_name + @page = "\n#{@page}" + end + @page = "\n#{@page}" if @code + end + + def time_cop_patch! + @page = "\n#{@page}" + end + + def application! + @page = "<%= javascript_include_tag '#{@javascript || 'application'}' %>\n#{@page}" + end + + def style_sheet! + @page = "<%= stylesheet_link_tag '#{@style_sheet || 'application'}' %>\n#{@page}" + end + + def go_function! + @page = "\n#{@page}" + end + + def escape_javascript(str) + ComponentTestHelpers.escape_javascript(str) + end + + def client_title! + title = + ComponentTestHelpers.escape_javascript(ComponentTestHelpers.current_example.description) + title = "#{title}...continued." if ComponentTestHelpers.description_displayed + + @page = "\n#{@page}" + + ComponentTestHelpers.description_displayed = true + end + + def html_block! + @page = "\n#{@html_block}\n#{@page}" + end + + def deliver! + @render_params[:inline] = @page + response.headers['Cache-Control'] = 'max-age=120' + response.headers['X-Tracking-ID'] = '123456' + render @render_params + end + + def server_only? + @render_on == :server_only + end + + def test + return unless initialize! + + # TODO: reverse the the layout in the above methods so they can run in + # the right order + mount_component! if @component_name + client_code! unless server_only? + time_cop_patch! if !server_only? || Lolex.initialized? + application! if (!server_only? && !@render_params[:layout]) || @javascript + style_sheet! if !@render_params[:layout] || @style_sheet + go_function! + client_title! if ComponentTestHelpers.current_example + html_block! + deliver! + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/expectations.rb b/ruby/hyper-spec/lib/hyper-spec/expectations.rb new file mode 100644 index 000000000..ba4c33227 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/expectations.rb @@ -0,0 +1,76 @@ +# don't put this in directory lib/rspec/ as that will cause stack overflow with rails/rspec loads +module RSpec + module Expectations + class ExpectationTarget + end + + module HyperSpecInstanceMethods + + def self.included(base) + base.include HyperSpec::ComponentTestHelpers + end + + def to_on_client(matcher, message = nil, &block) + evaluate_client('ruby').to(matcher, message, &block) + end + + alias on_client_to to_on_client + + def to_on_client_not(matcher, message = nil, &block) + evaluate_client('ruby').not_to(matcher, message, &block) + end + + alias on_client_to_not to_on_client_not + alias on_client_not_to to_on_client_not + alias to_not_on_client to_on_client_not + alias not_to_on_client to_on_client_not + + def to_then(matcher, message = nil, &block) + evaluate_client('promise').to(matcher, message, &block) + end + + alias then_to to_then + + def to_then_not(matcher, message = nil, &block) + evaluate_client('promise').not_to(matcher, message, &block) + end + + alias then_to_not to_then_not + alias then_not_to to_then_not + alias to_not_then to_then_not + alias not_to_then to_then_not + + private + + def evaluate_client(method) + source = add_opal_block(@args_str, @target) + value = @target.binding.eval("evaluate_#{method}(#{source.inspect}, {}, {})") + ExpectationTarget.for(value, nil) + end + end + + class OnClientWithArgsTarget + include HyperSpecInstanceMethods + + def initialize(target, args) + unless args.is_a? Hash + raise ExpectationNotMetError, + "You must pass a hash of local var, value pairs to the 'with' modifier" + end + + @target = target + @args_str = args.collect do |name, value| + set_local_var(name, value) + end.join("\n") + end + end + + class BlockExpectationTarget < ExpectationTarget + include HyperSpecInstanceMethods + + def with(args) + OnClientWithArgsTarget.new(@target, args) + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/patches.rb b/ruby/hyper-spec/lib/hyper-spec/patches.rb new file mode 100644 index 000000000..e88cc5111 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/patches.rb @@ -0,0 +1,68 @@ +module Unparser + class Emitter + # Emitter for send + class Send < self + def local_variable_clash? + selector =~ /^[A-Z]/ || local_variable_scope.local_variable_defined_for_node?(node, selector) + end + end + end +end + +module MethodSource + class << self + alias original_lines_for_before_hyper_spec lines_for + alias original_source_helper_before_hyper_spec source_helper + + def source_helper(source_location, name=nil) + source_location[1] = 1 if source_location[0] == '(pry)' + original_source_helper_before_hyper_spec source_location, name + end + + def lines_for(file_name, name = nil) + if file_name == '(pry)' + HyperSpec.current_pry_code_block + else + original_lines_for_before_hyper_spec file_name, name + end + end + end +end + +class Object + def opal_serialize + nil + end +end + +class Hash + def opal_serialize + "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}"}.join(', ')}}" + end +end + +class Array + def opal_serialize + "[#{collect { |v| v.opal_serialize }.join(', ')}]" + end +end + +[FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end +end + +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') + [Bignum, Fixnum].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end + end +end + +class Time + def to_opal_expression + "Time.parse('#{inspect}')" + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb b/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb deleted file mode 100644 index dc0a720b8..000000000 --- a/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Unparser - class Emitter - # Emitter for send - class Send < self - def local_variable_clash? - selector =~ /^[A-Z]/ || local_variable_scope.local_variable_defined_for_node?(node, selector) - end - end - end -end From ac01576828996f234ed40dd9a746bb63071bf81a Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 24 Jan 2021 15:51:22 -0500 Subject: [PATCH 065/307] cleanup --- ruby/hyper-spec/lib/hyper-spec/expectations.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/expectations.rb b/ruby/hyper-spec/lib/hyper-spec/expectations.rb index ba4c33227..e6a2898fd 100644 --- a/ruby/hyper-spec/lib/hyper-spec/expectations.rb +++ b/ruby/hyper-spec/lib/hyper-spec/expectations.rb @@ -5,7 +5,6 @@ class ExpectationTarget end module HyperSpecInstanceMethods - def self.included(base) base.include HyperSpec::ComponentTestHelpers end From 631bf62c15ae82f7d4c58b8c0c3b3ea9bbec0bbe Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 24 Jan 2021 18:17:19 -0500 Subject: [PATCH 066/307] more cleanup --- .../lib/hyper-spec/component_test_helpers.rb | 15 ++++++++------- .../lib/hyper-spec/controller_helpers.rb | 15 +++------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 855a9035b..18be6e402 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -8,10 +8,8 @@ def self.opal_compile(str) raise e end - extend ActionView::Helpers::JavaScriptHelper - - def opal_compile(s) - ComponentTestHelpers.opal_compile(s) + def opal_compile(str) + ComponentTestHelpers.opal_compile(str) end class << self @@ -23,9 +21,12 @@ def test_id @_hyperspec_private_test_id += 1 end - def display_example_description - "" + include ActionView::Helpers::JavaScriptHelper + + def current_example_description! + title = "#{title}...continued." if description_displayed + self.description_displayed = true + "#{escape_javascript(current_example.description)}#{title}" end def file_cache diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb index 32d736f6d..ee82f6abf 100644 --- a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -72,19 +72,10 @@ def go_function! "{window.hyper_spec_waiting_for_go = false}\n#{@page}" end - def escape_javascript(str) - ComponentTestHelpers.escape_javascript(str) - end - - def client_title! - title = - ComponentTestHelpers.escape_javascript(ComponentTestHelpers.current_example.description) - title = "#{title}...continued." if ComponentTestHelpers.description_displayed - + def example_title! + title = ComponentTestHelpers.current_example_description! @page = "\n#{@page}" - - ComponentTestHelpers.description_displayed = true end def html_block! @@ -113,7 +104,7 @@ def test application! if (!server_only? && !@render_params[:layout]) || @javascript style_sheet! if !@render_params[:layout] || @style_sheet go_function! - client_title! if ComponentTestHelpers.current_example + example_title! if ComponentTestHelpers.current_example html_block! deliver! end From a2472b2b50e542ec97f01a618aca32400c310bdd Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 27 Jan 2021 14:02:38 -0500 Subject: [PATCH 067/307] refactored with rack and rails helpers --- ruby/hyper-spec/lib/hyper-spec.rb | 1 + .../lib/hyper-spec/component_test_helpers.rb | 61 ++++------ .../lib/hyper-spec/controller_helpers.rb | 115 ++++++++++++------ ruby/hyper-spec/lib/hyper-spec/rack.rb | 57 +++++++++ .../hyper-spec/rails_controller_helpers.rb | 48 ++++++++ ruby/hyper-spec/spec/hyper_spec.rb | 2 +- 6 files changed, 209 insertions(+), 75 deletions(-) create mode 100644 ruby/hyper-spec/lib/hyper-spec/rack.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 9f7ed1ea0..652a8e68a 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -10,6 +10,7 @@ require 'hyper-spec/component_test_helpers' require 'hyper-spec/controller_helpers' require 'hyper-spec/patches' +require 'hyper-spec/rails_controller_helpers' require 'hyper-spec/version' require 'hyper-spec/wait_for_ajax' require 'hyper-spec/expectations' diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 18be6e402..5e63e3b88 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -48,51 +48,38 @@ def cache_delete(key) end end - # TODO: verify this works in all cases... - # at one time it was ApplicationController - # then it changed to ::ActionController::Base (going to rails 5?) - # but HyperModel has specs that depend on it being ApplicationController - # We could use the controller param in those cases, but it seems reasonable - # that by default we would use ApplicationController if it exists and fallback - # to ActionController::Base - - def new_controller - return ::HyperSpecTestController if defined?(::HyperSpecTestController) - - controller = if defined? ApplicationController - Class.new ApplicationController - elsif defined? ::ActionController::Base - Class.new ::ActionController::Base - end + # By default we assume we are operating in a Rails environment and will + # hook in using a rails controller. To override this define the + # HyperSpecController class in your spec helper. See the rack.rb file + # for an example of how to do this. - raise raise 'No HyperSpecTestController class found' unless controller + def hyper_spec_test_controller + return ::HyperSpecTestController if defined?(::HyperSpecTestController) - Object.const_set('HyperSpecTestController', controller) + base = if defined? ApplicationController + Class.new ApplicationController + elsif defined? ::ActionController::Base + Class.new ::ActionController::Base + else + raise "Unless using Rails you must define the HyperSpecTestController\n"\ + 'For rack apps try requiring hyper-spec/rack.' + end + Object.const_set('HyperSpecTestController', base) end - def add_rails_route(route_root) - routes = ::Rails.application.routes - routes.disable_clear_and_finalize = true - routes.clear! - routes.draw { get "/#{route_root}/:id", to: "#{route_root}#test" } - ::Rails.application.routes_reloader.paths.each { |path| load(path) } - routes.finalize! - ActiveSupport.on_load(:action_controller) { routes.finalize! } - ensure - routes.disable_clear_and_finalize = false + # First insure we have a controller, then make sure it responds to the test method + # if not, then add the rails specific controller methods. The RailsControllerHelpers + # module will automatically add a top level route back to the controller. + + def route_root_for(controller) + controller ||= hyper_spec_test_controller + controller.include RailsControllerHelpers unless controller.method_defined?(:test) + controller.route_root end def build_test_url_for(controller = nil, ping = nil) - controller ||= new_controller - - unless controller.method_defined?(:test) - controller.include ControllerHelpers - add_rails_route(controller.route_root) - end - id = ping ? 'ping' : ComponentTestHelpers.test_id - - "/#{controller.route_root}/#{id}" + "/#{route_root_for(controller)}/#{id}" end def insert_html(str) diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb index ee82f6abf..c6acb6401 100644 --- a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -1,10 +1,49 @@ +require 'pry' module HyperSpec + # Defines a series of methods that will build a test page + # This module is typically included into the HyperSpecTestController class. module ControllerHelpers - TOP_LEVEL_COMPONENT_PATCH = - Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) - TIME_COP_CLIENT_PATCH = - Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + - "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" + + # These methods are dependent on the stack being used. See the + # RailsControllerHelpers module and rack.rb for two implementations. + # Each method should append the appropriate data to the @page variable + + # return an empty 204 status + # either by setting headers or returning and appropriate response for rack. + def ping! + raise 'must implement' + end + + # return a script or style_sheet tag pointing to the application js code. + # typically you be pointing to a path on the server or using + # sprockets to deliver the file. + + def application!(_file_) + raise 'must implement' + end + + def style_sheet!(_file_) + raise 'must implement' + end + + # deliver the page. The @page variable will contain the html ready to go, + # any additional options that are passed through from the spec will be + # in the @render_params variable. For example layout: 'my special layout' + + def deliver! + raise 'must implement' + end + + # generate a react_render top level block. This will only be called if + # you use the mount directive in your specs, so it is optional. + + def mount_component! + raise 'mount_component not implemented in HyperSpecTestController' + end + + # by default the route back to the controller will be the controller name, less the + # word Controller, and underscored. If you want some other name redefine this + # method in the HyperSpecController class. def self.included(base) def base.route_root @@ -19,8 +58,21 @@ def base.route_root end end + # The remainder of the methods should work for most implementations. + + # helper method checking the render_on parameter + + def server_only? + @render_on == :server_only + end + + # The controllers behavior is kept as an array of values in the ComponentTestHelpers cache + # under a unique id for each test run. Grab the parameters and move them to instance vars + + # If this is just a ping! Then we can just exit with nil. + def initialize! - return ping! if params[:id] == 'ping' + return if params[:id] == 'ping' key = "/#{self.class.route_root}/#{params[:id]}" test_params = ComponentTestHelpers.cache_read(key) @@ -38,16 +90,22 @@ def initialize! @page = "\n" end - def ping! - head(:no_content) - nil - end + # add any html code generated by the insert_html directive - def mount_component! - @page = '<%= react_component @component_name, @component_params, '\ - "{ prerender: #{@render_on != :client_only} } %>\n#{@page}" + def html_block! + @page = "\n#{@html_block}\n#{@page}" end + # patch behavior of the HyperComponent TopLevelRailsComponent class + # so that things like events are passed back to the test harness + TOP_LEVEL_COMPONENT_PATCH = + Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) + + # patch time cop and lolex so they stay in sync across the client and server + TIME_COP_CLIENT_PATCH = + Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + + "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" + def client_code! if @component_name @page = "\n#{@page}" @@ -59,50 +117,33 @@ def time_cop_patch! @page = "\n#{@page}" end - def application! - @page = "<%= javascript_include_tag '#{@javascript || 'application'}' %>\n#{@page}" - end - - def style_sheet! - @page = "<%= stylesheet_link_tag '#{@style_sheet || 'application'}' %>\n#{@page}" - end + # Add the go_function to the client console. This is used to stop a hyper-spec pause directive. def go_function! @page = "\n#{@page}" end + # First lines displayed on the console will be the name of the spec + def example_title! title = ComponentTestHelpers.current_example_description! @page = "\n#{@page}" end - def html_block! - @page = "\n#{@html_block}\n#{@page}" - end - - def deliver! - @render_params[:inline] = @page - response.headers['Cache-Control'] = 'max-age=120' - response.headers['X-Tracking-ID'] = '123456' - render @render_params - end - - def server_only? - @render_on == :server_only - end + # generate each piece of the page, and then deliver it def test - return unless initialize! + return ping! unless initialize! # TODO: reverse the the layout in the above methods so they can run in # the right order mount_component! if @component_name client_code! unless server_only? time_cop_patch! if !server_only? || Lolex.initialized? - application! if (!server_only? && !@render_params[:layout]) || @javascript - style_sheet! if !@render_params[:layout] || @style_sheet + application!(@javascript || 'application') if (!server_only? && !@render_params[:layout]) || @javascript + style_sheet!(@style_sheet || 'application') if !@render_params[:layout] || @style_sheet go_function! example_title! if ComponentTestHelpers.current_example html_block! diff --git a/ruby/hyper-spec/lib/hyper-spec/rack.rb b/ruby/hyper-spec/lib/hyper-spec/rack.rb new file mode 100644 index 000000000..9d18f3139 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/rack.rb @@ -0,0 +1,57 @@ +require 'hyper-spec' + +class HyperSpecTestController < SimpleDelegator + include HyperSpec::ControllerHelpers + + class << self + attr_reader :sprocket_server + attr_reader :asset_path + + def wrap(app:, append_path: 'app', asset_path: '/assets') + @sprocket_server = Opal::Sprockets::Server.new do |s| + s.append_path append_path + end + + @asset_path = asset_path + + ::Rack::Builder.app(app) do + map "/#{HyperSpecTestController.route_root}" do + use HyperSpecTestController + end + end + end + end + + def sprocket_server + self.class.sprocket_server + end + + def asset_path + self.class.asset_path + end + + def ping! + [204, {}, []] + end + + def application!(file) + @page = Opal::Sprockets.javascript_include_tag( + file, + debug: true, + sprockets: sprocket_server.sprockets, + prefix: asset_path + ) + "\n#{@page}" + end + + def style_sheet!(_file_); end + + def deliver! + [200, { 'Content-Type' => 'text/html' }, [@page]] + end + + def call(env) + __setobj__(Rack::Request.new(env)) + params[:id] = path.split('/').last + test + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb new file mode 100644 index 000000000..9e0c4e870 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb @@ -0,0 +1,48 @@ +module HyperSpec + module RailsControllerHelpers + def self.included(base) + base.include ControllerHelpers + base.include Helpers + routes = ::Rails.application.routes + routes.disable_clear_and_finalize = true + routes.clear! + routes.draw { get "/#{base.route_root}/:id", to: "#{base.route_root}#test" } + ::Rails.application.routes_reloader.paths.each { |path| load(path) } + routes.finalize! + ActiveSupport.on_load(:action_controller) { routes.finalize! } + ensure + routes.disable_clear_and_finalize = false + end + + module Helpers + def ping! + head(:no_content) + nil + end + + def mount_component! + @page = '<%= react_component @component_name, @component_params, '\ + "{ prerender: #{@render_on != :client_only} } %>\n#{@page}" + end + + def application!(file) + @page = "<%= javascript_include_tag '#{file}' %>\n#{@page}" + end + + def style_sheet!(file) + @page = "<%= stylesheet_link_tag '#{file}' %>\n#{@page}" + end + + def deliver! + @render_params[:inline] = @page + response.headers['Cache-Control'] = 'max-age=120' + response.headers['X-Tracking-ID'] = '123456' + render @render_params + end + + def server_only? + @render_on == :server_only + end + end + end +end diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 4361dba41..68c7d2e03 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -227,7 +227,7 @@ class StyledDiv end end - context "new style rspec expressions" do + context "new style rspec expressions", no_reset: true do before(:each) do @str = 'hello' From 5c3d3454b14c2df953159d9a61518fa362614d0a Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 27 Jan 2021 21:18:17 -0500 Subject: [PATCH 068/307] easy to run rackup refactor complete --- ruby/hyper-i18n/hyper-i18n.gemspec | 1 + ruby/hyper-spec/hyper-spec.gemspec | 3 +- .../lib/hyper-spec/controller_helpers.rb | 65 +++++++++++-------- ruby/hyper-spec/lib/hyper-spec/rack.rb | 14 +++- .../hyper-spec/rails_controller_helpers.rb | 8 +-- .../hyperstack-config.gemspec | 1 + 6 files changed, 58 insertions(+), 34 deletions(-) diff --git a/ruby/hyper-i18n/hyper-i18n.gemspec b/ruby/hyper-i18n/hyper-i18n.gemspec index 790778bfd..d7a7e8051 100644 --- a/ruby/hyper-i18n/hyper-i18n.gemspec +++ b/ruby/hyper-i18n/hyper-i18n.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'puma' spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 end diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 2018b1fc2..911b673fc 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -33,7 +33,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'mini_racer', '~> 0.2.6' spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' spec.add_dependency 'parser', '>= 2.3.3.1' # on rails-6 this is now >= 2.3 - spec.add_dependency 'rspec-rails' + spec.add_dependency 'rspec' spec.add_dependency 'selenium-webdriver' spec.add_dependency 'timecop', '~> 0.8.1' spec.add_dependency 'uglifier' @@ -50,6 +50,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.3.0', '< 2.5.0' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' spec.add_development_dependency 'rspec-its' diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb index c6acb6401..e2d864716 100644 --- a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -1,9 +1,7 @@ -require 'pry' module HyperSpec # Defines a series of methods that will build a test page # This module is typically included into the HyperSpecTestController class. module ControllerHelpers - # These methods are dependent on the stack being used. See the # RailsControllerHelpers module and rack.rb for two implementations. # Each method should append the appropriate data to the @page variable @@ -14,11 +12,16 @@ def ping! raise 'must implement' end - # return a script or style_sheet tag pointing to the application js code. + def json! + # this can be a no-op but if json included by the application, + # hyper-spec will fail, with an error complaining about to_json + end + + # return a script or style_sheet tag pointing to some asset. # typically you be pointing to a path on the server or using # sprockets to deliver the file. - def application!(_file_) + def require!(_file_) raise 'must implement' end @@ -62,8 +65,8 @@ def base.route_root # helper method checking the render_on parameter - def server_only? - @render_on == :server_only + def on_client? + @render_on != :server_only end # The controllers behavior is kept as an array of values in the ComponentTestHelpers cache @@ -87,66 +90,74 @@ def initialize! @javascript = @render_params.delete(:javascript) @code = @render_params.delete(:code) - @page = "\n" + @page = [''] end # add any html code generated by the insert_html directive def html_block! - @page = "\n#{@html_block}\n#{@page}" + @page << @html_block end # patch behavior of the HyperComponent TopLevelRailsComponent class # so that things like events are passed back to the test harness TOP_LEVEL_COMPONENT_PATCH = - Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) + Opal.compile(File.read(File.expand_path('../sources/top_level_rails_component.rb', __dir__))) # patch time cop and lolex so they stay in sync across the client and server TIME_COP_CLIENT_PATCH = - Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + - "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" + Opal.compile(File.read(File.expand_path('../hyper-spec/time_cop.rb', __dir__))) + + "\n#{File.read(File.expand_path('../sources/lolex.js', __dir__))}" def client_code! if @component_name - @page = "\n#{@page}" + @page << "" end - @page = "\n#{@page}" if @code + @page << "" if @code end def time_cop_patch! - @page = "\n#{@page}" + @page << "" end # Add the go_function to the client console. This is used to stop a hyper-spec pause directive. def go_function! - @page = "\n#{@page}" + @page << "' end # First lines displayed on the console will be the name of the spec def example_title! title = ComponentTestHelpers.current_example_description! - @page = "\n#{@page}" + @page << "" end # generate each piece of the page, and then deliver it + def style_sheet_file + @style_sheet || (!@render_params[:layout] && 'application') + end + + def application_file + @javascript || (on_client? && !@render_params[:layout] && 'application') + end + def test return ping! unless initialize! - # TODO: reverse the the layout in the above methods so they can run in - # the right order - mount_component! if @component_name - client_code! unless server_only? - time_cop_patch! if !server_only? || Lolex.initialized? - application!(@javascript || 'application') if (!server_only? && !@render_params[:layout]) || @javascript - style_sheet!(@style_sheet || 'application') if !@render_params[:layout] || @style_sheet - go_function! - example_title! if ComponentTestHelpers.current_example html_block! + example_title! if ComponentTestHelpers.current_example + go_function! if on_client? + style_sheet!(style_sheet_file) if style_sheet_file + application!(application_file) if application_file + json! # MUST BE AFTER application_file which + time_cop_patch! if on_client? || Lolex.initialized? + client_code! if on_client? + mount_component! if @component_name + @page = @page.join("\n") + "\n\n" deliver! end end diff --git a/ruby/hyper-spec/lib/hyper-spec/rack.rb b/ruby/hyper-spec/lib/hyper-spec/rack.rb index 9d18f3139..14fed7e37 100644 --- a/ruby/hyper-spec/lib/hyper-spec/rack.rb +++ b/ruby/hyper-spec/lib/hyper-spec/rack.rb @@ -35,14 +35,24 @@ def ping! end def application!(file) - @page = Opal::Sprockets.javascript_include_tag( + @page << Opal::Sprockets.javascript_include_tag( file, debug: true, sprockets: sprocket_server.sprockets, prefix: asset_path - ) + "\n#{@page}" + ) end + def json! + @page << Opal::Sprockets.javascript_include_tag( + 'json', + debug: true, + sprockets: sprocket_server.sprockets, + prefix: asset_path + ) + end + + def style_sheet!(_file_); end def deliver! diff --git a/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb index 9e0c4e870..af953f5c0 100644 --- a/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb @@ -21,16 +21,16 @@ def ping! end def mount_component! - @page = '<%= react_component @component_name, @component_params, '\ - "{ prerender: #{@render_on != :client_only} } %>\n#{@page}" + @page << '<%= react_component @component_name, @component_params, '\ + "{ prerender: #{@render_on != :client_only} } %>" end def application!(file) - @page = "<%= javascript_include_tag '#{file}' %>\n#{@page}" + @page << "<%= javascript_include_tag '#{file}' %>" end def style_sheet!(file) - @page = "<%= stylesheet_link_tag '#{file}' %>\n#{@page}" + @page << "<%= stylesheet_link_tag '#{file}' %>" end def deliver! diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 8ad3af09e..40b9621b8 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec', '~> 3.7.0' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' From 74a7f81176b826514ad3ebe88172963da7773156 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 29 Jan 2021 09:28:29 -0500 Subject: [PATCH 069/307] better no_reset handling, added to_js method --- .travis.yml | 2 +- ruby/hyper-spec/lib/hyper-spec.rb | 3 ++ .../lib/hyper-spec/component_test_helpers.rb | 30 ++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9927bc5af..9bee9afb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ _test_gem: &_test_gem # must remove this zombie for new yarn to work - sudo rm -f /usr/local/bin/yarn - nvm install 10 - - rvm install 2.5.1 + - rvm install 2.6.3 # was 2.5.1 - gem install bundler - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver before_script: diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 652a8e68a..b6aa1c414 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -97,6 +97,9 @@ def reset_sessions! config.before(:all, no_reset: true) do HyperSpec.reset_between_examples = false end + config.before(:all, no_reset: false) do + HyperSpec.reset_between_examples = true + end config.after(:all) do HyperSpec.reset_sessions! unless HyperSpec.reset_between_examples? end diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 5e63e3b88..25576ce06 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -137,10 +137,38 @@ def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) alias c? evaluate_ruby - # TODO: add a compile_ruby method. refactor evaluate_ruby and expect_evaluate ruby to use common methods + # TODO: add a to_js method. refactor evaluate_ruby and expect_evaluate ruby to use common methods # that process the params, and produce a ruby code string, and a resulting JS string # compile_ruby can be useful in seeing what code opal produces... + def to_js(p1 = nil, p2 = nil, p3 = nil, &block) + insure_page_loaded + # TODO: better error message here...either you give us a block + # or first argument must be a hash or a string. + if p1.is_a? Hash + str = '' + p3 = p2 + p2 = p1 + else + str = p1 + end + if p3 + opts = p2 + args = p3 + elsif p2 + opts = {} + args = p2 + else + opts = args = {} + end + + args.each do |name, value| + str = "#{set_local_var(name, value)}\n#{str}" + end + str = add_opal_block(str, block) if block + opal_compile(str) + end + def expect_evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) insure_page_loaded if p1.is_a? Hash From 3e3b62b1ac0cc21e95428e352ce094e5365beaf6 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 29 Jan 2021 09:49:49 -0500 Subject: [PATCH 070/307] travis unique errors working around --- ruby/hyper-spec/lib/hyper-spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index b6aa1c414..d22ab308b 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -24,6 +24,17 @@ nil end +module Capybara + module Selenium + module DeprecationSuppressor + def deprecate(*args, &block) + puts "!!!!!!!!!!!!DEPRECATION MESSAGE: #{args}" + super unless @suppress_for_capybara + end + end + end +end + Parser::Builders::Default.emit_procarg0 = true # not available in parser 2.3 From 8ffd0c74b50eddf83315704176357bc12776c105 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 29 Jan 2021 09:58:29 -0500 Subject: [PATCH 071/307] travis unique errors working around round 2 --- ruby/hyper-spec/lib/hyper-spec.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index d22ab308b..d8ab0277a 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -24,12 +24,18 @@ nil end -module Capybara - module Selenium - module DeprecationSuppressor - def deprecate(*args, &block) - puts "!!!!!!!!!!!!DEPRECATION MESSAGE: #{args}" - super unless @suppress_for_capybara +module Selenium + module WebDriver + class Logger + def deprecate(old, new = nil, **) + message = +"[DEPRECATION] #{old} is deprecated" + message << if new + ". Use #{new} instead." + else + ' and will be removed in the next releases.' + end + + warn message end end end From a1b2080370f2bbfd45d87a07516b204abff3ebc6 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 2 Feb 2021 12:57:15 -0500 Subject: [PATCH 072/307] removed hard dependencies on libv8 and mini_racer --- ruby/hyper-component/hyper-component.gemspec | 4 ++-- .../spec/client_features/dsl_spec.rb | 6 +++--- .../spec/client_features/element_spec.rb | 2 +- .../spec/client_features/server_spec.rb | 2 +- .../spec/client_features/state_spec.rb | 2 +- .../isomorphic/isomorphic_helpers_spec.rb | 6 +++--- .../isomorphic/rails/component_mount_spec.rb | 2 +- .../contextual_renderer_spec.rb | 2 +- ruby/hyper-component/spec/spec_helper.rb | 12 +++++++++++ ruby/hyper-i18n/hyper-i18n.gemspec | 1 + ruby/hyper-i18n/spec/hyper_i18n_spec.rb | 2 +- ruby/hyper-i18n/spec/spec_helper.rb | 12 +++++++++++ ruby/hyper-model/hyper-model.gemspec | 1 + .../spec/batch3/aaa_edge_cases_spec.rb | 4 ++-- ruby/hyper-model/spec/spec_helper.rb | 21 +++++++++++++++++++ .../config/initializers/synchromesh.rb | 20 ------------------ .../spec/hyper-router/basic_dsl_spec.rb | 2 +- ruby/hyper-router/spec/spec_helper.rb | 11 ++++++++++ .../config/initializers/hyperstack.rb | 3 --- ruby/hyper-spec/hyper-spec.gemspec | 2 +- ruby/hyper-spec/spec/hyper_spec.rb | 2 +- ruby/hyper-spec/spec/spec_helper.rb | 11 ++++++++++ ruby/hyper-state/hyper-state.gemspec | 2 +- ruby/hyper-store/hyper-store.gemspec | 2 +- .../hyperstack-config.gemspec | 2 +- ruby/rails-hyperstack/lib/rails-hyperstack.rb | 2 +- .../rails-hyperstack/rails-hyperstack.gemspec | 4 ++-- 27 files changed, 94 insertions(+), 48 deletions(-) delete mode 100644 ruby/hyper-model/spec/test_app/config/initializers/synchromesh.rb delete mode 100644 ruby/hyper-router/spec/test_app/config/initializers/hyperstack.rb diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index 49296406d..fd1a06bc5 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -23,12 +23,12 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-state', Hyperstack::Component::VERSION spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION - spec.add_dependency 'libv8', '~> 7.3.492.27.1' - spec.add_dependency 'mini_racer', '~> 0.2.6' + # spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'bundler' + spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-spec', Hyperstack::Component::VERSION spec.add_development_dependency 'jquery-rails' diff --git a/ruby/hyper-component/spec/client_features/dsl_spec.rb b/ruby/hyper-component/spec/client_features/dsl_spec.rb index 590a38d34..16c996d21 100644 --- a/ruby/hyper-component/spec/client_features/dsl_spec.rb +++ b/ruby/hyper-component/spec/client_features/dsl_spec.rb @@ -55,7 +55,7 @@ class Foo expect(page.body[-60..-19]).to include('
hello
') end - it "in prerender will pass converted props through event handlers" do + it "in prerender will pass converted props through event handlers", :prerendering_on do client_option render_on: :both mount 'Foo' do class Foo @@ -94,7 +94,7 @@ class Foo expect(page.body[-70..-19]).to include('
hellogoodby
') end - it "in prerendering has a .br short hand String method" do + it "in prerendering has a .br short hand String method", :prerendering_on do client_option render_on: :server_only client_option raise_on_js_errors: :off mount 'Foo' do @@ -186,7 +186,7 @@ class Comp; end .to match(/Comp does not appear to be a react component./) end - it 'raises a method missing error' do + it 'raises a method missing error', :prerendering_on do client_option render_on: :both client_option raise_on_js_errors: :off expect_evaluate_ruby do diff --git a/ruby/hyper-component/spec/client_features/element_spec.rb b/ruby/hyper-component/spec/client_features/element_spec.rb index b4390dc28..1ba9c8eac 100644 --- a/ruby/hyper-component/spec/client_features/element_spec.rb +++ b/ruby/hyper-component/spec/client_features/element_spec.rb @@ -18,7 +18,7 @@ end describe "Event Subscription" do - it "keeps the original params, and ignores false, nil, and blank event names" do + it "keeps the original params, and ignores false, nil, and blank event names", :prerendering_on do client_option render_on: :both mount 'Foo' do class Foo diff --git a/ruby/hyper-component/spec/client_features/server_spec.rb b/ruby/hyper-component/spec/client_features/server_spec.rb index 6ff1774f8..d57c05b2f 100644 --- a/ruby/hyper-component/spec/client_features/server_spec.rb +++ b/ruby/hyper-component/spec/client_features/server_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe 'React::Server', js: true do +describe 'React::Server', :js, :prerendering_on do describe "render_to_string" do it "should render a React.Element to string" do diff --git a/ruby/hyper-component/spec/client_features/state_spec.rb b/ruby/hyper-component/spec/client_features/state_spec.rb index 3c0a6db35..e3dacde8f 100644 --- a/ruby/hyper-component/spec/client_features/state_spec.rb +++ b/ruby/hyper-component/spec/client_features/state_spec.rb @@ -16,7 +16,7 @@ class << self end.to eq('bar') end - it 'ignores state updates during rendering' do + it 'ignores state updates during rendering', :prerendering_on do client_option render_on: :both evaluate_ruby do class StateTest < Hyperloop::Component diff --git a/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb b/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb index 35eb380e9..f12b0bf3a 100644 --- a/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb +++ b/ruby/hyper-component/spec/isomorphic/isomorphic_helpers_spec.rb @@ -132,7 +132,7 @@ def self.valediction end ]} - it 'raises an error when react cannot be loaded' do + it 'raises an error when react cannot be loaded', :prerendering_on do context = described_class.new('unique-id', v8_context, controller, name) context.instance_variable_set(:@ctx, test_context) expect { @@ -140,7 +140,7 @@ def self.valediction }.to raise_error(/No Hyperstack components found/) end - it 'executes method with args inside opal rubyracer context' do + it 'executes method with args inside opal rubyracer context', :prerendering_on do ctx = react_context context = described_class.new('unique-id', ctx, controller, name) context.eval(opal_code) @@ -148,7 +148,7 @@ def self.valediction expect(result).to eq('Hello, world!') end - it 'executes the method inside opal rubyracer context' do + it 'executes the method inside opal rubyracer context', :prerendering_on do ctx = react_context context = described_class.new('unique-id', ctx, controller, name) context.eval(opal_code) diff --git a/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb b/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb index 42470e37f..44b89a824 100644 --- a/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb +++ b/ruby/hyper-component/spec/isomorphic/rails/component_mount_spec.rb @@ -13,7 +13,7 @@ expect(html).to match(/<\/div>/) end - it 'accepts a pre-render option' do + it 'accepts a pre-render option', :prerendering_on do html = helper.react_component('Components::HelloWorld', {}, prerender: true) expect(html).to match(/Hello, World!<\/span><\/div>/) end diff --git a/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb b/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb index 9d11a2133..91d2e689c 100644 --- a/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb +++ b/ruby/hyper-component/spec/isomorphic/server_rendering/contextual_renderer_spec.rb @@ -5,7 +5,7 @@ let(:init) { Proc.new {} } let(:options) { { context_initializer: init } } - describe '#render' do + describe '#render', :prerendering_on do it 'pre-renders HTML' do result = renderer.render('Components.Todo', { todo: 'finish reactive-ruby' }, diff --git a/ruby/hyper-component/spec/spec_helper.rb b/ruby/hyper-component/spec/spec_helper.rb index c90ce845a..711eacab6 100644 --- a/ruby/hyper-component/spec/spec_helper.rb +++ b/ruby/hyper-component/spec/spec_helper.rb @@ -15,6 +15,7 @@ require 'opal-browser' require 'timecop' + RSpec.configure do |config| config.color = true config.fail_fast = ENV['FAIL_FAST'] || false @@ -32,6 +33,17 @@ Rails.cache.clear end + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end + config.filter_run_including focus: true config.filter_run_excluding opal: true config.run_all_when_everything_filtered = true diff --git a/ruby/hyper-i18n/hyper-i18n.gemspec b/ruby/hyper-i18n/hyper-i18n.gemspec index 790778bfd..ad8298cf3 100644 --- a/ruby/hyper-i18n/hyper-i18n.gemspec +++ b/ruby/hyper-i18n/hyper-i18n.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-model', Hyperstack::I18n::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::I18n::VERSION + spec.add_development_dependency 'mini_racer' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0.0' spec.add_development_dependency 'pry' spec.add_development_dependency 'puma' diff --git a/ruby/hyper-i18n/spec/hyper_i18n_spec.rb b/ruby/hyper-i18n/spec/hyper_i18n_spec.rb index c74e7e4fe..6a85d00c4 100644 --- a/ruby/hyper-i18n/spec/hyper_i18n_spec.rb +++ b/ruby/hyper-i18n/spec/hyper_i18n_spec.rb @@ -22,7 +22,7 @@ class TestComponent end end [['component rendering', :client_only], ['prerendering', :server_only]].each do |mode, flag| - it "will translate during #{mode}" do + it "will translate during #{mode}", prerendering_on: flag == :server_only do mount 'Components::TestComponent', {}, render_on: flag expect(find('#tp1')).to have_content('I am a key') expect(find('#tp2')).to have_content('Hello world') diff --git a/ruby/hyper-i18n/spec/spec_helper.rb b/ruby/hyper-i18n/spec/spec_helper.rb index 9927231b8..cb505b28b 100644 --- a/ruby/hyper-i18n/spec/spec_helper.rb +++ b/ruby/hyper-i18n/spec/spec_helper.rb @@ -9,6 +9,18 @@ require 'hyper-i18n' RSpec.configure do |config| + + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end + config.color = true config.formatter = :documentation config.before(:all) do diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 7329a92e4..99c76614b 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -33,6 +33,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'factory_bot_rails' spec.add_development_dependency 'hyper-spec', HyperModel::VERSION + spec.add_development_dependency 'mini_racer' spec.add_development_dependency 'mysql2' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' diff --git a/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb b/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb index e23381c5e..768adce75 100644 --- a/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb +++ b/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb @@ -45,7 +45,7 @@ end - it "prerenders a belongs to relationship" do + it "prerenders a belongs to relationship", :prerendering_on do # must be first otherwise check for ajax fails because of race condition # with previous test user_item = User.create(name: 'Fred') @@ -86,7 +86,7 @@ class PrerenderTest < HyperComponent end.to eq(1) end - it "fetches data during prerendering" do + it "fetches data during prerendering", :prerendering_on do 5.times do |i| FactoryBot.create(:todo, title: "Todo #{i}") end diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index 378895490..fe04f7b01 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -26,6 +26,27 @@ def self.log_import(s) end end + config.before :suite do + # grab the prerendered .js file, for debugging purposes + class MiniRacer::Context + alias original_eval eval + def eval(str, options = nil) + original_eval str, options + rescue Exception => e + File.write('react_prerendering_src.js', str) rescue nil + raise e + end + end + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end + config.color = true config.fail_fast = ENV['FAIL_FAST'] || false config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures") diff --git a/ruby/hyper-model/spec/test_app/config/initializers/synchromesh.rb b/ruby/hyper-model/spec/test_app/config/initializers/synchromesh.rb deleted file mode 100644 index 8d6858222..000000000 --- a/ruby/hyper-model/spec/test_app/config/initializers/synchromesh.rb +++ /dev/null @@ -1,20 +0,0 @@ -# require 'pusher' -# Pusher.app_id = "MY_TEST_ID" -# Pusher.key = "MY_TEST_KEY" -# Pusher.secret = "MY_TEST_SECRET" -# require 'pusher-fake' -# -# HyperMesh.configuration do |config| -# config.transport = :pusher -# config.channel_prefix = "synchromesh" -# config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) -# end -class MiniRacer::Context - alias original_eval eval - def eval(str, options=nil) - original_eval str, options - rescue Exception => e - File.write('react_prerendering_src.js', str) rescue nil - raise e - end -end diff --git a/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb b/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb index b40a81785..b3635b056 100644 --- a/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb +++ b/ruby/hyper-router/spec/hyper-router/basic_dsl_spec.rb @@ -20,7 +20,7 @@ end [:server_only, :client_only].each do |render_on| - it "a routers render method can return a string (#{render_on})" do + it "a routers render method can return a string (#{render_on})", prerendering_on: render_on == :server_only do client_option render_on: render_on mount 'SimpleStringRouter' expect(page).to have_content('a simple string') diff --git a/ruby/hyper-router/spec/spec_helper.rb b/ruby/hyper-router/spec/spec_helper.rb index 6fbe79626..b600a261c 100644 --- a/ruby/hyper-router/spec/spec_helper.rb +++ b/ruby/hyper-router/spec/spec_helper.rb @@ -16,6 +16,17 @@ RSpec.configure do |config| + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end + config.after :each do Rails.cache.clear end diff --git a/ruby/hyper-router/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-router/spec/test_app/config/initializers/hyperstack.rb deleted file mode 100644 index a70c483de..000000000 --- a/ruby/hyper-router/spec/test_app/config/initializers/hyperstack.rb +++ /dev/null @@ -1,3 +0,0 @@ -Hyperstack.configuration do |config| - config.prerendering = :on -end diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index d1af270f0..573f36e6d 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -29,7 +29,6 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'filecache' spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'method_source' - spec.add_dependency 'mini_racer', '~> 0.2.6' spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' spec.add_dependency 'parser', '>= 2.3.3.1' # on rails-6 this is now >= 2.3 spec.add_dependency 'rspec-rails' @@ -41,6 +40,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_development_dependency 'bundler' spec.add_development_dependency 'hyper-component', HyperSpec::VERSION + spec.add_development_dependency 'mini_racer' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4' spec.add_development_dependency 'pry-rescue' diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 4361dba41..1e4dfbc9b 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -22,7 +22,7 @@ class ShowOff context "the client_option method" do - it "can render server side only" do + it "can render server side only", :prerendering_on do client_option render_on: :server_only mount 'SayHello', name: 'George' expect(page).to have_content('Hello there George') diff --git a/ruby/hyper-spec/spec/spec_helper.rb b/ruby/hyper-spec/spec/spec_helper.rb index aceee18f8..413bbed44 100644 --- a/ruby/hyper-spec/spec/spec_helper.rb +++ b/ruby/hyper-spec/spec/spec_helper.rb @@ -44,4 +44,15 @@ def adjusted(width, height) # config.after :each do # Rails.cache.clear # end + + config.before :suite do + MiniRacer_Backup = MiniRacer + Object.send(:remove_const, :MiniRacer) + end + + config.around(:each, :prerendering_on) do |example| + MiniRacer = MiniRacer_Backup + example.run + Object.send(:remove_const, :MiniRacer) + end end diff --git a/ruby/hyper-state/hyper-state.gemspec b/ruby/hyper-state/hyper-state.gemspec index 3e13f9121..9ed133d8a 100644 --- a/ruby/hyper-state/hyper-state.gemspec +++ b/ruby/hyper-state/hyper-state.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'hyper-component', Hyperstack::State::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::State::VERSION spec.add_development_dependency 'listen' - spec.add_development_dependency 'mini_racer', '~> 0.2.4' + # spec.add_development_dependency 'mini_racer', '~> 0.2.4' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' diff --git a/ruby/hyper-store/hyper-store.gemspec b/ruby/hyper-store/hyper-store.gemspec index 77a472c9d..ea3191680 100644 --- a/ruby/hyper-store/hyper-store.gemspec +++ b/ruby/hyper-store/hyper-store.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'hyper-component', Hyperstack::Legacy::Store::VERSION spec.add_development_dependency 'hyper-spec', Hyperstack::Legacy::Store::VERSION spec.add_development_dependency 'listen' - spec.add_development_dependency 'mini_racer', '~> 0.2.6' + # spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'opal-browser', '~> 0.2.0' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 8ad3af09e..4c79330c1 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency 'listen', '~> 3.0' # for hot loader - spec.add_dependency 'mini_racer', '~> 0.2.6' + # spec.add_dependency 'mini_racer', '~> 0.2.6' spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_dependency 'uglifier' diff --git a/ruby/rails-hyperstack/lib/rails-hyperstack.rb b/ruby/rails-hyperstack/lib/rails-hyperstack.rb index 1a356de67..8fa1f90d0 100644 --- a/ruby/rails-hyperstack/lib/rails-hyperstack.rb +++ b/ruby/rails-hyperstack/lib/rails-hyperstack.rb @@ -21,11 +21,11 @@ require 'opal-rails' require 'hyper-model' require 'hyper-router' + require 'mini_racer' rescue LoadError end require 'react-rails' require 'opal-browser' -require 'mini_racer' require 'hyperstack/version' class RailsHyperstack < Rails::Railtie diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 5eaf26b30..469153b20 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -61,8 +61,8 @@ You can control how much of the stack gets installed as well: spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' - spec.add_dependency 'mini_racer', '~> 0.2.6' - spec.add_dependency 'libv8', '~> 7.3.492.27.1' + # spec.add_dependency 'mini_racer', '~> 0.2.6' + # spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'bundler' From 234d14858047d4764203aa1d67c9c769a4f54e28 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 2 Feb 2021 15:56:53 -0500 Subject: [PATCH 073/307] added error message if MiniRacer is missing --- ruby/hyper-component/hyper-component.gemspec | 2 +- .../component/rails/server_rendering/contextual_renderer.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index fd1a06bc5..4fd919aed 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -28,12 +28,12 @@ Gem::Specification.new do |spec| spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' spec.add_development_dependency 'bundler' - spec.add_development_dependency 'mini_racer', '~> 0.2.6' spec.add_development_dependency 'chromedriver-helper' spec.add_development_dependency 'hyper-spec', Hyperstack::Component::VERSION spec.add_development_dependency 'jquery-rails' spec.add_development_dependency 'listen' spec.add_development_dependency 'mime-types' + spec.add_development_dependency 'mini_racer' spec.add_development_dependency 'nokogiri' spec.add_development_dependency 'opal-jquery' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb index 0c415e347..b9159e596 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/contextual_renderer.rb @@ -13,6 +13,10 @@ def self.context_instance_for(context) class ContextualRenderer < React::ServerRendering::BundleRenderer def initialize(options = {}) + unless v8_runtime? + raise "Hyperstack prerendering only works with MiniRacer. Add 'mini_racer' to your Gemfile" + end + super({ files: ['hyperstack-prerender-loader.js'] }.merge(options)) ComponentLoader.new(v8_context).load end From 1aebde62684e3076464b8785689b20be79ff09ef Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 2 Feb 2021 16:14:02 -0500 Subject: [PATCH 074/307] removed deprecated driver_path option --- ruby/hyper-spec/lib/hyper-spec.rb | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index d8ab0277a..8375bfa73 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -24,23 +24,6 @@ nil end -module Selenium - module WebDriver - class Logger - def deprecate(old, new = nil, **) - message = +"[DEPRECATION] #{old} is deprecated" - message << if new - ". Use #{new} instead." - else - ' and will be removed in the next releases.' - end - - warn message - end - end - end -end - Parser::Builders::Default.emit_procarg0 = true # not available in parser 2.3 @@ -201,9 +184,8 @@ def self.on_server? options.add_argument('--headless') options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') - Capybara::Selenium::Driver.new( - app, browser: :chrome, driver_path: '/usr/lib/chromium-browser/chromedriver', options: options - ) + Selenium::WebDriver::Chrome::Service.driver_path = '/usr/lib/chromium-browser/chromedriver' + Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end Capybara.register_driver :firefox_headless do |app| From 883e0b71c02ed3d7dff5cd240e8933e95b2071e4 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 2 Feb 2021 16:43:14 -0500 Subject: [PATCH 075/307] include rspec/rails in rails-hyperstack dev gem set as its no longer in hyper-spec --- ruby/rails-hyperstack/rails-hyperstack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 5eaf26b30..091860fb3 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -71,7 +71,7 @@ You can control how much of the stack gets installed as well: spec.add_development_dependency 'pry' spec.add_development_dependency 'puma' spec.add_development_dependency 'bootsnap' - spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4' # was 1.3.6 -- see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'sass-rails', '~> 5.0' From 0bf8d3bb3116964a2fd4c1a18a9cf99f58cf4b3e Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 2 Feb 2021 16:47:11 -0500 Subject: [PATCH 076/307] refactored and improved hyperspec --- .travis.yml | 2 +- ruby/hyper-i18n/hyper-i18n.gemspec | 1 + ruby/hyper-spec/hyper-spec.gemspec | 4 +- ruby/hyper-spec/lib/hyper-spec.rb | 102 +++-- .../lib/hyper-spec/component_test_helpers.rb | 371 ++++-------------- .../lib/hyper-spec/controller_helpers.rb | 164 ++++++++ .../hyper-spec/lib/hyper-spec/expectations.rb | 75 ++++ ruby/hyper-spec/lib/hyper-spec/patches.rb | 68 ++++ ruby/hyper-spec/lib/hyper-spec/rack.rb | 67 ++++ .../hyper-spec/rails_controller_helpers.rb | 48 +++ .../lib/hyper-spec/unparser_patch.rb | 10 - ruby/hyper-spec/spec/hyper_spec.rb | 2 +- .../hyperstack-config.gemspec | 1 + .../rails-hyperstack/rails-hyperstack.gemspec | 2 +- 14 files changed, 567 insertions(+), 350 deletions(-) create mode 100644 ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/expectations.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/patches.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/rack.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb delete mode 100644 ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb diff --git a/.travis.yml b/.travis.yml index 9927bc5af..9bee9afb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ _test_gem: &_test_gem # must remove this zombie for new yarn to work - sudo rm -f /usr/local/bin/yarn - nvm install 10 - - rvm install 2.5.1 + - rvm install 2.6.3 # was 2.5.1 - gem install bundler - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver before_script: diff --git a/ruby/hyper-i18n/hyper-i18n.gemspec b/ruby/hyper-i18n/hyper-i18n.gemspec index ad8298cf3..b7d62e9bd 100644 --- a/ruby/hyper-i18n/hyper-i18n.gemspec +++ b/ruby/hyper-i18n/hyper-i18n.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'puma' spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 end diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index 573f36e6d..b86e9fce3 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] + spec.add_dependency 'actionview' spec.add_dependency 'capybara' spec.add_dependency 'chromedriver-helper', '1.2.0' spec.add_dependency 'filecache' @@ -31,7 +32,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'method_source' spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' spec.add_dependency 'parser', '>= 2.3.3.1' # on rails-6 this is now >= 2.3 - spec.add_dependency 'rspec-rails' + spec.add_dependency 'rspec' spec.add_dependency 'selenium-webdriver' spec.add_dependency 'timecop', '~> 0.8.1' spec.add_dependency 'uglifier' @@ -49,6 +50,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'react-rails', '>= 2.3.0', '< 2.5.0' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rspec-collection_matchers' spec.add_development_dependency 'rspec-expectations' spec.add_development_dependency 'rspec-its' diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 1f2c76353..8375bfa73 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -1,13 +1,47 @@ -require 'capybara/rspec' +# hyper-spec +require 'action_view' require 'opal' -require 'selenium-webdriver' +require 'unparser' +require 'method_source' +require 'hyper-spec/time_cop.rb' +require 'filecache' +require 'capybara/rspec' require 'hyper-spec/component_test_helpers' +require 'hyper-spec/controller_helpers' +require 'hyper-spec/patches' +require 'hyper-spec/rails_controller_helpers' require 'hyper-spec/version' require 'hyper-spec/wait_for_ajax' +require 'hyper-spec/expectations' +require 'parser/current' require 'selenium/web_driver/firefox/profile' +require 'selenium-webdriver' + +begin + require 'pry' +rescue LoadError + nil +end + +Parser::Builders::Default.emit_procarg0 = true + +# not available in parser 2.3 +if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 + Parser::Builders::Default.emit_arg_inside_procarg0 = true +end module HyperSpec + if defined? Pry + # add a before eval hook to pry so we can capture the source + class << self + attr_accessor :current_pry_code_block + Pry.hooks.add_hook(:before_eval, 'hyper_spec_code_capture') do |code| + HyperSpec.current_pry_code_block = code + end + end + end + def self.reset_between_examples=(value) RSpec.configuration.reset_between_examples = value end @@ -21,7 +55,8 @@ def self.reset_sessions! end end -# TODO: figure out why we need this patch - its because we are on an old version of Selenium Webdriver, but why? +# TODO: figure out why we need this patch - its because we are on an old version +# of Selenium Webdriver, but why? require 'selenium-webdriver' module Selenium @@ -33,7 +68,7 @@ module Bridge COMMANDS.freeze def log(type) - data = execute :get_log, {}, {type: type.to_s} + data = execute :get_log, {}, type: type.to_s Array(data).map do |l| begin @@ -48,10 +83,9 @@ def log(type) end end - module Capybara class << self - alias_method :old_reset_sessions!, :reset_sessions! + alias old_reset_sessions! reset_sessions! def reset_sessions! old_reset_sessions! if HyperSpec.reset_between_examples? end @@ -63,6 +97,9 @@ def reset_sessions! config.before(:all, no_reset: true) do HyperSpec.reset_between_examples = false end + config.before(:all, no_reset: false) do + HyperSpec.reset_between_examples = true + end config.after(:all) do HyperSpec.reset_sessions! unless HyperSpec.reset_between_examples? end @@ -80,19 +117,22 @@ def reset_sessions! config.add_setting :debugger_width, default: nil - config.before(:each) do - Hyperstack.class_eval do - def self.on_server? - true + if defined?(Hyperstack) + Hyperstack.class_eval do + def self.on_server? + true + end end - end if defined?(Hyperstack) + end # for compatibility with HyperMesh - HyperMesh.class_eval do - def self.on_server? - true + if defined?(HyperMesh) + HyperMesh.class_eval do + def self.on_server? + true + end end - end if defined?(HyperMesh) + end end config.before(:each, js: true) do @@ -112,6 +152,11 @@ def self.on_server? # Capybara config RSpec.configure do |config| + config.before(:each) do |example| + HyperSpec::ComponentTestHelpers.current_example = example + HyperSpec::ComponentTestHelpers.description_displayed = false + end + config.add_setting :wait_for_initialization_time config.wait_for_initialization_time = 3 @@ -121,28 +166,15 @@ def self.on_server? options = {} options.merge!( w3c: false, - args: %w[auto-open-devtools-for-tabs]) #, - #prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - #) unless ENV['NO_DEBUGGER'] - # this does not seem to work properly. Don't document this feature yet. - # options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] + args: %w[auto-open-devtools-for-tabs] + ) options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options, 'goog:loggingPrefs' => {browser: 'ALL'}) + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: options, 'goog:loggingPrefs' => { browser: 'ALL' } + ) Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) end - # Capybara.register_driver :chrome do |app| - # options = {} - # options.merge!( - # args: %w[auto-open-devtools-for-tabs], - # prefs: { 'devtools.open_docked' => false, "devtools.currentDockState" => "undocked", devtools: {currentDockState: :undocked} } - # ) unless ENV['NO_DEBUGGER'] - # # this does not seem to work properly. Don't document this feature yet. - # options['mobileEmulation'] = { 'deviceName' => ENV['DEVICE'].tr('-', ' ') } if ENV['DEVICE'] - # capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(chromeOptions: options) - # Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities) - # end - Capybara.register_driver :firefox do |app| Capybara::Selenium::Driver.new(app, browser: :firefox) end @@ -152,7 +184,8 @@ def self.on_server? options.add_argument('--headless') options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') - Capybara::Selenium::Driver.new(app, browser: :chrome, :driver_path => "/usr/lib/chromium-browser/chromedriver", options: options) + Selenium::WebDriver::Chrome::Service.driver_path = '/usr/lib/chromium-browser/chromedriver' + Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end Capybara.register_driver :firefox_headless do |app| @@ -184,5 +217,4 @@ def self.on_server? when 'travis' then :chrome_headless_docker_travis else :selenium_chrome_headless end - end diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 5e2e43985..25576ce06 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -1,110 +1,17 @@ # see component_test_helpers_spec.rb for examples -require 'parser/current' -require 'unparser' -require 'hyper-spec/unparser_patch' # not present in original version of refactored hyperspec -require 'method_source' -begin - require 'pry' -rescue LoadError - nil -end -require_relative '../../lib/hyper-spec/time_cop.rb' -require 'filecache' - -Parser::Builders::Default.emit_procarg0 = true # not present in original version of refactored hyperspec -if Parser::Builders::Default.respond_to? :emit_arg_inside_procarg0 - Parser::Builders::Default.emit_arg_inside_procarg0 = true # not available in parser 2.3 -end - -module MethodSource - class << self - alias original_lines_for_before_hyper_spec lines_for - alias original_source_helper_before_hyper_spec source_helper - - def source_helper(source_location, name=nil) - source_location[1] = 1 if source_location[0] == '(pry)' - original_source_helper_before_hyper_spec source_location, name - end - - def lines_for(file_name, name = nil) - if file_name == '(pry)' - HyperSpec.current_pry_code_block - else - original_lines_for_before_hyper_spec file_name, name - end - end - end -end - -class Object - def opal_serialize - nil - end -end - -class Hash - def opal_serialize - "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}"}.join(', ')}}" - end -end - -class Array - def opal_serialize - "[#{collect { |v| v.opal_serialize }.join(', ')}]" - end -end - -[FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass].each do |klass| - klass.send(:define_method, :opal_serialize) do - inspect - end -end - -if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') - [Bignum, Fixnum].each do |klass| - klass.send(:define_method, :opal_serialize) do - inspect - end - end -end - -class Time - def to_opal_expression - "Time.parse('#{inspect}')" - end -end - module HyperSpec - - # add a before eval hook to pry so we can capture the source - if defined? Pry - class << self - attr_accessor :current_pry_code_block - Pry.hooks.add_hook(:before_eval, "hyper_spec_code_capture") do |code| - HyperSpec.current_pry_code_block = code - end - end - end - module ComponentTestHelpers - def self.opal_compile(s) - Opal.compile(s) + def self.opal_compile(str) + Opal.compile(str) rescue Exception => e - puts "puts could not compile: \n\n#{s}\n\n" + puts "puts could not compile: \n\n#{str}\n\n" raise e end - def opal_compile(s) - ComponentTestHelpers.opal_compile(s) + def opal_compile(str) + ComponentTestHelpers.opal_compile(str) end - TOP_LEVEL_COMPONENT_PATCH = - Opal.compile(File.read(File.expand_path('../../sources/top_level_rails_component.rb', __FILE__))) - TIME_COP_CLIENT_PATCH = - Opal.compile(File.read(File.expand_path('../../hyper-spec/time_cop.rb', __FILE__))) + - "\n#{File.read(File.expand_path('../../sources/lolex.js', __FILE__))}" - - class << self attr_accessor :current_example attr_accessor :description_displayed @@ -114,148 +21,65 @@ def test_id @_hyperspec_private_test_id += 1 end - def display_example_description - "" + include ActionView::Helpers::JavaScriptHelper + + def current_example_description! + title = "#{title}...continued." if description_displayed + self.description_displayed = true + "#{escape_javascript(current_example.description)}#{title}" end - RAILS_CACHE = false def file_cache @file_cache ||= FileCache.new("cache", "/tmp/hyper-spec-caches", 30, 3) end def cache_read(key) - if RAILS_CACHE - ::Rails.cache.read(key) - else - file_cache.get(key) - end + file_cache.get(key) end def cache_write(key, value) - if RAILS_CACHE - ::Rails.cache.write(key, value) - else - file_cache.set(key, value) - end + file_cache.set(key, value) end def cache_delete(key) - if RAILS_CACHE - ::Rails.cache.write(key, value) - else - file_cache.delete(key) - end + file_cache.delete(key) rescue StandardError nil end end - # TODO: verify this works in all cases... - # at one time it was ApplicationController - # then it changed to ::ActionController::Base (going to rails 5?) - # but HyperModel has specs that depend on it being ApplicationController - # We could use the controller param in those cases, but it seems reasonable - # that by default we would use ApplicationController if it exists and fallback - # to ActionController::Base - - def new_controller - if defined? ApplicationController - Class.new ApplicationController - else - Class.new ::ActionController::Base - end - end + # By default we assume we are operating in a Rails environment and will + # hook in using a rails controller. To override this define the + # HyperSpecController class in your spec helper. See the rack.rb file + # for an example of how to do this. - def build_test_url_for(controller, ping = nil) - unless controller - unless defined?(::HyperSpecTestController) - Object.const_set('HyperSpecTestController', new_controller) - end + def hyper_spec_test_controller + return ::HyperSpecTestController if defined?(::HyperSpecTestController) - controller = ::HyperSpecTestController - end - - route_root = controller.name.gsub(/Controller$/, '').underscore - - unless controller.method_defined?(:test) - controller.class_eval do - define_method(:test) do - head(:no_content) && return if params[:id] == 'ping' - route_root = self.class.name.gsub(/Controller$/, '').underscore - key = "/#{route_root}/#{params[:id]}" - test_params = ComponentTestHelpers.cache_read(key) - @component_name = test_params[0] - @component_params = test_params[1] - html_block = test_params[2] - render_params = test_params[3] - render_on = render_params.delete(:render_on) || :client_only - _mock_time = render_params.delete(:mock_time) - style_sheet = render_params.delete(:style_sheet) - javascript = render_params.delete(:javascript) - code = render_params.delete(:code) - #page = "#{html_block}\n\n" - page = "\n" - - page = '<%= react_component @component_name, @component_params, '\ - "{ prerender: #{render_on != :client_only} } %>\n#{page}" if @component_name - #page = "\n#{page}" - unless render_on == :server_only - page = "\n#{page}" if @component_name - page = "\n#{page}" if code - end - - if render_on != :server_only || Lolex.initialized? - page = "\n#{page}" - end - - if (render_on != :server_only && !render_params[:layout]) || javascript - page = "<%= javascript_include_tag '#{javascript || 'application'}' %>\n#{page}" - end - - if !render_params[:layout] || style_sheet - page = "<%= stylesheet_link_tag '#{style_sheet || 'application'}' %>\n#{page}" - end - page = "\n#{page}" - - if ComponentTestHelpers.current_example - - title = view_context.escape_javascript(ComponentTestHelpers.current_example.description) - title = "#{title}...continued." if ComponentTestHelpers.description_displayed + base = if defined? ApplicationController + Class.new ApplicationController + elsif defined? ::ActionController::Base + Class.new ::ActionController::Base + else + raise "Unless using Rails you must define the HyperSpecTestController\n"\ + 'For rack apps try requiring hyper-spec/rack.' + end + Object.const_set('HyperSpecTestController', base) + end - page = "\n#{page}" + # First insure we have a controller, then make sure it responds to the test method + # if not, then add the rails specific controller methods. The RailsControllerHelpers + # module will automatically add a top level route back to the controller. - ComponentTestHelpers.description_displayed = true - end - page = "\n#{html_block}\n#{page}" - render_params[:inline] = page - response.headers['Cache-Control'] = 'max-age=120' - response.headers['X-Tracking-ID'] = '123456' - render render_params - end - end + def route_root_for(controller) + controller ||= hyper_spec_test_controller + controller.include RailsControllerHelpers unless controller.method_defined?(:test) + controller.route_root + end - begin - routes = ::Rails.application.routes - routes.disable_clear_and_finalize = true - routes.clear! - routes.draw do - get "/#{route_root}/:id", to: "#{route_root}#test" - end - ::Rails.application.routes_reloader.paths.each { |path| load(path) } - routes.finalize! - ActiveSupport.on_load(:action_controller) { routes.finalize! } - ensure - routes.disable_clear_and_finalize = false - end - end - if ping - "/#{route_root}/ping" - else - "/#{route_root}/#{ComponentTestHelpers.test_id}" - end + def build_test_url_for(controller = nil, ping = nil) + id = ping ? 'ping' : ComponentTestHelpers.test_id + "/#{route_root_for(controller)}/#{id}" end def insert_html(str) @@ -313,10 +137,38 @@ def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) alias c? evaluate_ruby - # TODO: add a compile_ruby method. refactor evaluate_ruby and expect_evaluate ruby to use common methods + # TODO: add a to_js method. refactor evaluate_ruby and expect_evaluate ruby to use common methods # that process the params, and produce a ruby code string, and a resulting JS string # compile_ruby can be useful in seeing what code opal produces... + def to_js(p1 = nil, p2 = nil, p3 = nil, &block) + insure_page_loaded + # TODO: better error message here...either you give us a block + # or first argument must be a hash or a string. + if p1.is_a? Hash + str = '' + p3 = p2 + p2 = p1 + else + str = p1 + end + if p3 + opts = p2 + args = p3 + elsif p2 + opts = {} + args = p2 + else + opts = args = {} + end + + args.each do |name, value| + str = "#{set_local_var(name, value)}\n#{str}" + end + str = add_opal_block(str, block) if block + opal_compile(str) + end + def expect_evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) insure_page_loaded if p1.is_a? Hash @@ -653,87 +505,4 @@ def attributes_on_client(model) evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys end end - - RSpec.configure do |config| - config.before(:each) do |example| - ComponentTestHelpers.current_example = example - ComponentTestHelpers.description_displayed = false - end - end -end - -module RSpec - module Expectations - class ExpectationTarget - end - - module HyperSpecInstanceMethods - - def self.included(base) - base.include HyperSpec::ComponentTestHelpers - end - - def to_on_client(matcher, message = nil, &block) - evaluate_client('ruby').to(matcher, message, &block) - end - - alias on_client_to to_on_client - - def to_on_client_not(matcher, message = nil, &block) - evaluate_client('ruby').not_to(matcher, message, &block) - end - - alias on_client_to_not to_on_client_not - alias on_client_not_to to_on_client_not - alias to_not_on_client to_on_client_not - alias not_to_on_client to_on_client_not - - def to_then(matcher, message = nil, &block) - evaluate_client('promise').to(matcher, message, &block) - end - - alias then_to to_then - - def to_then_not(matcher, message = nil, &block) - evaluate_client('promise').not_to(matcher, message, &block) - end - - alias then_to_not to_then_not - alias then_not_to to_then_not - alias to_not_then to_then_not - alias not_to_then to_then_not - - private - - def evaluate_client(method) - source = add_opal_block(@args_str, @target) - value = @target.binding.eval("evaluate_#{method}(#{source.inspect}, {}, {})") - ExpectationTarget.for(value, nil) - end - end - - class OnClientWithArgsTarget - include HyperSpecInstanceMethods - - def initialize(target, args) - unless args.is_a? Hash - raise ExpectationNotMetError, - "You must pass a hash of local var, value pairs to the 'with' modifier" - end - - @target = target - @args_str = args.collect do |name, value| - set_local_var(name, value) - end.join("\n") - end - end - - class BlockExpectationTarget < ExpectationTarget - include HyperSpecInstanceMethods - - def with(args) - OnClientWithArgsTarget.new(@target, args) - end - end - end end diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb new file mode 100644 index 000000000..e2d864716 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -0,0 +1,164 @@ +module HyperSpec + # Defines a series of methods that will build a test page + # This module is typically included into the HyperSpecTestController class. + module ControllerHelpers + # These methods are dependent on the stack being used. See the + # RailsControllerHelpers module and rack.rb for two implementations. + # Each method should append the appropriate data to the @page variable + + # return an empty 204 status + # either by setting headers or returning and appropriate response for rack. + def ping! + raise 'must implement' + end + + def json! + # this can be a no-op but if json included by the application, + # hyper-spec will fail, with an error complaining about to_json + end + + # return a script or style_sheet tag pointing to some asset. + # typically you be pointing to a path on the server or using + # sprockets to deliver the file. + + def require!(_file_) + raise 'must implement' + end + + def style_sheet!(_file_) + raise 'must implement' + end + + # deliver the page. The @page variable will contain the html ready to go, + # any additional options that are passed through from the spec will be + # in the @render_params variable. For example layout: 'my special layout' + + def deliver! + raise 'must implement' + end + + # generate a react_render top level block. This will only be called if + # you use the mount directive in your specs, so it is optional. + + def mount_component! + raise 'mount_component not implemented in HyperSpecTestController' + end + + # by default the route back to the controller will be the controller name, less the + # word Controller, and underscored. If you want some other name redefine this + # method in the HyperSpecController class. + + def self.included(base) + def base.route_root + # Implement underscore without using rails underscore, so we don't have a + # dependency on ActiveSupport + name.gsub(/Controller$/, '') + .gsub(/::/, '/') + .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + .gsub(/([a-z\d])([A-Z])/, '\1_\2') + .tr('-', '_') + .downcase + end + end + + # The remainder of the methods should work for most implementations. + + # helper method checking the render_on parameter + + def on_client? + @render_on != :server_only + end + + # The controllers behavior is kept as an array of values in the ComponentTestHelpers cache + # under a unique id for each test run. Grab the parameters and move them to instance vars + + # If this is just a ping! Then we can just exit with nil. + + def initialize! + return if params[:id] == 'ping' + + key = "/#{self.class.route_root}/#{params[:id]}" + test_params = ComponentTestHelpers.cache_read(key) + + @component_name = test_params[0] + @component_params = test_params[1] + @html_block = test_params[2] + @render_params = test_params[3] + @render_on = @render_params.delete(:render_on) || :client_only + @_mock_time = @render_params.delete(:mock_time) + @style_sheet = @render_params.delete(:style_sheet) + @javascript = @render_params.delete(:javascript) + @code = @render_params.delete(:code) + + @page = [''] + end + + # add any html code generated by the insert_html directive + + def html_block! + @page << @html_block + end + + # patch behavior of the HyperComponent TopLevelRailsComponent class + # so that things like events are passed back to the test harness + TOP_LEVEL_COMPONENT_PATCH = + Opal.compile(File.read(File.expand_path('../sources/top_level_rails_component.rb', __dir__))) + + # patch time cop and lolex so they stay in sync across the client and server + TIME_COP_CLIENT_PATCH = + Opal.compile(File.read(File.expand_path('../hyper-spec/time_cop.rb', __dir__))) + + "\n#{File.read(File.expand_path('../sources/lolex.js', __dir__))}" + + def client_code! + if @component_name + @page << "" + end + @page << "" if @code + end + + def time_cop_patch! + @page << "" + end + + # Add the go_function to the client console. This is used to stop a hyper-spec pause directive. + + def go_function! + @page << "' + end + + # First lines displayed on the console will be the name of the spec + + def example_title! + title = ComponentTestHelpers.current_example_description! + @page << "" + end + + # generate each piece of the page, and then deliver it + + def style_sheet_file + @style_sheet || (!@render_params[:layout] && 'application') + end + + def application_file + @javascript || (on_client? && !@render_params[:layout] && 'application') + end + + def test + return ping! unless initialize! + + html_block! + example_title! if ComponentTestHelpers.current_example + go_function! if on_client? + style_sheet!(style_sheet_file) if style_sheet_file + application!(application_file) if application_file + json! # MUST BE AFTER application_file which + time_cop_patch! if on_client? || Lolex.initialized? + client_code! if on_client? + mount_component! if @component_name + @page = @page.join("\n") + "\n\n" + deliver! + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/expectations.rb b/ruby/hyper-spec/lib/hyper-spec/expectations.rb new file mode 100644 index 000000000..e6a2898fd --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/expectations.rb @@ -0,0 +1,75 @@ +# don't put this in directory lib/rspec/ as that will cause stack overflow with rails/rspec loads +module RSpec + module Expectations + class ExpectationTarget + end + + module HyperSpecInstanceMethods + def self.included(base) + base.include HyperSpec::ComponentTestHelpers + end + + def to_on_client(matcher, message = nil, &block) + evaluate_client('ruby').to(matcher, message, &block) + end + + alias on_client_to to_on_client + + def to_on_client_not(matcher, message = nil, &block) + evaluate_client('ruby').not_to(matcher, message, &block) + end + + alias on_client_to_not to_on_client_not + alias on_client_not_to to_on_client_not + alias to_not_on_client to_on_client_not + alias not_to_on_client to_on_client_not + + def to_then(matcher, message = nil, &block) + evaluate_client('promise').to(matcher, message, &block) + end + + alias then_to to_then + + def to_then_not(matcher, message = nil, &block) + evaluate_client('promise').not_to(matcher, message, &block) + end + + alias then_to_not to_then_not + alias then_not_to to_then_not + alias to_not_then to_then_not + alias not_to_then to_then_not + + private + + def evaluate_client(method) + source = add_opal_block(@args_str, @target) + value = @target.binding.eval("evaluate_#{method}(#{source.inspect}, {}, {})") + ExpectationTarget.for(value, nil) + end + end + + class OnClientWithArgsTarget + include HyperSpecInstanceMethods + + def initialize(target, args) + unless args.is_a? Hash + raise ExpectationNotMetError, + "You must pass a hash of local var, value pairs to the 'with' modifier" + end + + @target = target + @args_str = args.collect do |name, value| + set_local_var(name, value) + end.join("\n") + end + end + + class BlockExpectationTarget < ExpectationTarget + include HyperSpecInstanceMethods + + def with(args) + OnClientWithArgsTarget.new(@target, args) + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/patches.rb b/ruby/hyper-spec/lib/hyper-spec/patches.rb new file mode 100644 index 000000000..e88cc5111 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/patches.rb @@ -0,0 +1,68 @@ +module Unparser + class Emitter + # Emitter for send + class Send < self + def local_variable_clash? + selector =~ /^[A-Z]/ || local_variable_scope.local_variable_defined_for_node?(node, selector) + end + end + end +end + +module MethodSource + class << self + alias original_lines_for_before_hyper_spec lines_for + alias original_source_helper_before_hyper_spec source_helper + + def source_helper(source_location, name=nil) + source_location[1] = 1 if source_location[0] == '(pry)' + original_source_helper_before_hyper_spec source_location, name + end + + def lines_for(file_name, name = nil) + if file_name == '(pry)' + HyperSpec.current_pry_code_block + else + original_lines_for_before_hyper_spec file_name, name + end + end + end +end + +class Object + def opal_serialize + nil + end +end + +class Hash + def opal_serialize + "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}"}.join(', ')}}" + end +end + +class Array + def opal_serialize + "[#{collect { |v| v.opal_serialize }.join(', ')}]" + end +end + +[FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end +end + +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') + [Bignum, Fixnum].each do |klass| + klass.send(:define_method, :opal_serialize) do + inspect + end + end +end + +class Time + def to_opal_expression + "Time.parse('#{inspect}')" + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/rack.rb b/ruby/hyper-spec/lib/hyper-spec/rack.rb new file mode 100644 index 000000000..14fed7e37 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/rack.rb @@ -0,0 +1,67 @@ +require 'hyper-spec' + +class HyperSpecTestController < SimpleDelegator + include HyperSpec::ControllerHelpers + + class << self + attr_reader :sprocket_server + attr_reader :asset_path + + def wrap(app:, append_path: 'app', asset_path: '/assets') + @sprocket_server = Opal::Sprockets::Server.new do |s| + s.append_path append_path + end + + @asset_path = asset_path + + ::Rack::Builder.app(app) do + map "/#{HyperSpecTestController.route_root}" do + use HyperSpecTestController + end + end + end + end + + def sprocket_server + self.class.sprocket_server + end + + def asset_path + self.class.asset_path + end + + def ping! + [204, {}, []] + end + + def application!(file) + @page << Opal::Sprockets.javascript_include_tag( + file, + debug: true, + sprockets: sprocket_server.sprockets, + prefix: asset_path + ) + end + + def json! + @page << Opal::Sprockets.javascript_include_tag( + 'json', + debug: true, + sprockets: sprocket_server.sprockets, + prefix: asset_path + ) + end + + + def style_sheet!(_file_); end + + def deliver! + [200, { 'Content-Type' => 'text/html' }, [@page]] + end + + def call(env) + __setobj__(Rack::Request.new(env)) + params[:id] = path.split('/').last + test + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb new file mode 100644 index 000000000..af953f5c0 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb @@ -0,0 +1,48 @@ +module HyperSpec + module RailsControllerHelpers + def self.included(base) + base.include ControllerHelpers + base.include Helpers + routes = ::Rails.application.routes + routes.disable_clear_and_finalize = true + routes.clear! + routes.draw { get "/#{base.route_root}/:id", to: "#{base.route_root}#test" } + ::Rails.application.routes_reloader.paths.each { |path| load(path) } + routes.finalize! + ActiveSupport.on_load(:action_controller) { routes.finalize! } + ensure + routes.disable_clear_and_finalize = false + end + + module Helpers + def ping! + head(:no_content) + nil + end + + def mount_component! + @page << '<%= react_component @component_name, @component_params, '\ + "{ prerender: #{@render_on != :client_only} } %>" + end + + def application!(file) + @page << "<%= javascript_include_tag '#{file}' %>" + end + + def style_sheet!(file) + @page << "<%= stylesheet_link_tag '#{file}' %>" + end + + def deliver! + @render_params[:inline] = @page + response.headers['Cache-Control'] = 'max-age=120' + response.headers['X-Tracking-ID'] = '123456' + render @render_params + end + + def server_only? + @render_on == :server_only + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb b/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb deleted file mode 100644 index dc0a720b8..000000000 --- a/ruby/hyper-spec/lib/hyper-spec/unparser_patch.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Unparser - class Emitter - # Emitter for send - class Send < self - def local_variable_clash? - selector =~ /^[A-Z]/ || local_variable_scope.local_variable_defined_for_node?(node, selector) - end - end - end -end diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 1e4dfbc9b..97f10c545 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -227,7 +227,7 @@ class StyledDiv end end - context "new style rspec expressions" do + context "new style rspec expressions", no_reset: true do before(:each) do @str = 'hello' diff --git a/ruby/hyperstack-config/hyperstack-config.gemspec b/ruby/hyperstack-config/hyperstack-config.gemspec index 4c79330c1..55f0e6a08 100644 --- a/ruby/hyperstack-config/hyperstack-config.gemspec +++ b/ruby/hyperstack-config/hyperstack-config.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec', '~> 3.7.0' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'timecop', '~> 0.8.1' diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index 469153b20..b38872ea9 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -71,7 +71,7 @@ You can control how much of the stack gets installed as well: spec.add_development_dependency 'pry' spec.add_development_dependency 'puma' spec.add_development_dependency 'bootsnap' - spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4' # was 1.3.6 -- see https://github.com/rails/rails/issues/35153 spec.add_development_dependency 'sass-rails', '~> 5.0' From 1a8dcbfae17679e52bd7413644fb653ea8943730 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 3 Feb 2021 15:11:44 -0500 Subject: [PATCH 077/307] refactored and removed special promise methods --- .../lib/hyper-spec/component_test_helpers.rb | 137 ++++++------------ .../hyper-spec/lib/hyper-spec/expectations.rb | 33 ++--- ruby/hyper-spec/lib/hyper-spec/time_cop.rb | 2 + ruby/hyper-spec/spec/hyper_spec.rb | 5 +- 4 files changed, 60 insertions(+), 117 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 25576ce06..0c2ae2353 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -1,7 +1,8 @@ # see component_test_helpers_spec.rb for examples module HyperSpec module ComponentTestHelpers - def self.opal_compile(str) + def self.opal_compile(str, _opts = nil) + # _opts allows second dummy param to be passed from to_js Opal.compile(str) rescue Exception => e puts "puts could not compile: \n\n#{str}\n\n" @@ -103,36 +104,21 @@ def set_local_var(name, object) end end - def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) + def evaluate_ruby(*args, &block) insure_page_loaded - # TODO: better error message here...either you give us a block - # or first argument must be a hash or a string. - if p1.is_a? Hash - str = '' - p3 = p2 - p2 = p1 - else - str = p1 - end - if p3 - opts = p2 - args = p3 - elsif p2 - opts = {} - args = p2 - else - opts = args = {} - end - - args.each do |name, value| - str = "#{set_local_var(name, value)}\n#{str}" - end - str = add_opal_block(str, block) if block + str, opts = process_params(*args, &block) + str = add_promise_wrapper(str) js = opal_compile(str).gsub("// Prepare super implicit arguments\n", '') .delete("\n").gsub('(Opal);', '(Opal)') - # workaround for firefox 58 and geckodriver 0.19.1, because firefox is unable to find .$to_json: - # JSON.parse(evaluate_script("(function(){var a=Opal.Array.$new(); a[0]=#{js}; return a.$to_json();})();"), opts).first - JSON.parse(evaluate_script("[#{js}].$to_json()"), opts).first + page.execute_script("window.hyper_spec_promise_result = false; #{js}") + Timeout.timeout(Capybara.default_max_wait_time) do + loop do + break if page.evaluate_script('!!window.hyper_spec_promise_result') + + sleep 0.25 + end + end + JSON.parse(page.evaluate_script("window.hyper_spec_promise_result.$to_json()"), opts).first end alias c? evaluate_ruby @@ -141,56 +127,22 @@ def evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) # that process the params, and produce a ruby code string, and a resulting JS string # compile_ruby can be useful in seeing what code opal produces... - def to_js(p1 = nil, p2 = nil, p3 = nil, &block) - insure_page_loaded - # TODO: better error message here...either you give us a block - # or first argument must be a hash or a string. - if p1.is_a? Hash - str = '' - p3 = p2 - p2 = p1 - else - str = p1 - end - if p3 - opts = p2 - args = p3 - elsif p2 - opts = {} - args = p2 - else - opts = args = {} - end - - args.each do |name, value| - str = "#{set_local_var(name, value)}\n#{str}" - end - str = add_opal_block(str, block) if block - opal_compile(str) + def to_js(*args, &block) + opal_compile *process_params(*args, &block) end - def expect_evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) - insure_page_loaded - if p1.is_a? Hash - str = '' - p3 = p2 - p2 = p1 - else - str = p1 - end - if p3 - opts = p2 - args = p3 - elsif p2 - opts = {} - args = p2 - else - opts = args = {} - end - args.each do |name, value| + def process_params(*args, &block) + args = ['', *args] if args[0].is_a? Hash + args = [args[0], {}, args[1] || {}] if args.length < 3 + str, opts, vars = args + vars.each do |name, value| str = "#{name} = #{value.inspect}\n#{str}" end - expect(evaluate_ruby(add_opal_block(str, block), opts, {})) + [add_opal_block(str, block), opts] + end + + def expect_evaluate_ruby(*args, &block) + expect(evaluate_ruby(*args, &block)) end PRIVATE_VARIABLES = %i[ @@ -199,6 +151,21 @@ def expect_evaluate_ruby(p1 = nil, p2 = nil, p3 = nil, &block) b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_ ] + def add_promise_wrapper(str) +< Date: Wed, 3 Feb 2021 15:54:46 -0500 Subject: [PATCH 078/307] Only look for Promise if Promise is loaded --- ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb index 0c2ae2353..55ac9f1b1 100644 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb @@ -154,7 +154,7 @@ def expect_evaluate_ruby(*args, &block) def add_promise_wrapper(str) < Date: Thu, 4 Feb 2021 10:59:43 -0500 Subject: [PATCH 079/307] more refactoring, cleanup and documentation --- ruby/hyper-spec/lib/hyper-spec.rb | 26 +- .../lib/hyper-spec/component_test_helpers.rb | 457 ------------------ .../lib/hyper-spec/controller_helpers.rb | 20 +- .../hyper-spec/lib/hyper-spec/expectations.rb | 2 +- ruby/hyper-spec/lib/hyper-spec/helpers.rb | 242 ++++++++++ .../hyper-spec/internal/client_execution.rb | 133 +++++ .../hyper-spec/internal/component_mount.rb | 122 +++++ .../lib/hyper-spec/internal/controller.rb | 70 +++ .../lib/hyper-spec/{ => internal}/patches.rb | 24 +- .../internal/rails_controller_helpers.rb | 50 ++ .../lib/hyper-spec/{ => internal}/time_cop.rb | 6 +- .../lib/hyper-spec/internal/window_sizing.rb | 23 + .../hyper-spec/rails_controller_helpers.rb | 48 -- ruby/hyper-spec/spec/hyper_spec.rb | 8 +- 14 files changed, 696 insertions(+), 535 deletions(-) delete mode 100644 ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/helpers.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb create mode 100644 ruby/hyper-spec/lib/hyper-spec/internal/controller.rb rename ruby/hyper-spec/lib/hyper-spec/{ => internal}/patches.rb (61%) create mode 100644 ruby/hyper-spec/lib/hyper-spec/internal/rails_controller_helpers.rb rename ruby/hyper-spec/lib/hyper-spec/{ => internal}/time_cop.rb (96%) create mode 100644 ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb delete mode 100644 ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 8375bfa73..720054a1c 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -3,21 +3,31 @@ require 'opal' require 'unparser' require 'method_source' -require 'hyper-spec/time_cop.rb' require 'filecache' require 'capybara/rspec' -require 'hyper-spec/component_test_helpers' +require 'hyper-spec/internal/client_execution' +require 'hyper-spec/internal/component_mount' +require 'hyper-spec/internal/controller' +require 'hyper-spec/internal/patches' +require 'hyper-spec/internal/rails_controller_helpers' +require 'hyper-spec/internal/time_cop.rb' +require 'hyper-spec/internal/window_sizing' + require 'hyper-spec/controller_helpers' -require 'hyper-spec/patches' -require 'hyper-spec/rails_controller_helpers' -require 'hyper-spec/version' + require 'hyper-spec/wait_for_ajax' + +require 'hyper-spec/helpers' require 'hyper-spec/expectations' + require 'parser/current' require 'selenium/web_driver/firefox/profile' require 'selenium-webdriver' +require 'hyper-spec/version' + + begin require 'pry' rescue LoadError @@ -109,7 +119,7 @@ def reset_sessions! end RSpec.configure do |config| - config.include HyperSpec::ComponentTestHelpers + config.include HyperSpec::Helpers config.include HyperSpec::WaitForAjax config.include Capybara::DSL @@ -153,8 +163,8 @@ def self.on_server? # Capybara config RSpec.configure do |config| config.before(:each) do |example| - HyperSpec::ComponentTestHelpers.current_example = example - HyperSpec::ComponentTestHelpers.description_displayed = false + HyperSpec::Internal::Controller.current_example = example + HyperSpec::Internal::Controller.description_displayed = false end config.add_setting :wait_for_initialization_time diff --git a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb deleted file mode 100644 index 55ac9f1b1..000000000 --- a/ruby/hyper-spec/lib/hyper-spec/component_test_helpers.rb +++ /dev/null @@ -1,457 +0,0 @@ -# see component_test_helpers_spec.rb for examples -module HyperSpec - module ComponentTestHelpers - def self.opal_compile(str, _opts = nil) - # _opts allows second dummy param to be passed from to_js - Opal.compile(str) - rescue Exception => e - puts "puts could not compile: \n\n#{str}\n\n" - raise e - end - - def opal_compile(str) - ComponentTestHelpers.opal_compile(str) - end - - class << self - attr_accessor :current_example - attr_accessor :description_displayed - - def test_id - @_hyperspec_private_test_id ||= 0 - @_hyperspec_private_test_id += 1 - end - - include ActionView::Helpers::JavaScriptHelper - - def current_example_description! - title = "#{title}...continued." if description_displayed - self.description_displayed = true - "#{escape_javascript(current_example.description)}#{title}" - end - - def file_cache - @file_cache ||= FileCache.new("cache", "/tmp/hyper-spec-caches", 30, 3) - end - - def cache_read(key) - file_cache.get(key) - end - - def cache_write(key, value) - file_cache.set(key, value) - end - - def cache_delete(key) - file_cache.delete(key) - rescue StandardError - nil - end - end - - # By default we assume we are operating in a Rails environment and will - # hook in using a rails controller. To override this define the - # HyperSpecController class in your spec helper. See the rack.rb file - # for an example of how to do this. - - def hyper_spec_test_controller - return ::HyperSpecTestController if defined?(::HyperSpecTestController) - - base = if defined? ApplicationController - Class.new ApplicationController - elsif defined? ::ActionController::Base - Class.new ::ActionController::Base - else - raise "Unless using Rails you must define the HyperSpecTestController\n"\ - 'For rack apps try requiring hyper-spec/rack.' - end - Object.const_set('HyperSpecTestController', base) - end - - # First insure we have a controller, then make sure it responds to the test method - # if not, then add the rails specific controller methods. The RailsControllerHelpers - # module will automatically add a top level route back to the controller. - - def route_root_for(controller) - controller ||= hyper_spec_test_controller - controller.include RailsControllerHelpers unless controller.method_defined?(:test) - controller.route_root - end - - def build_test_url_for(controller = nil, ping = nil) - id = ping ? 'ping' : ComponentTestHelpers.test_id - "/#{route_root_for(controller)}/#{id}" - end - - def insert_html(str) - @_hyperspec_private_html_block = "#{@_hyperspec_private_html_block}\n#{str}" - end - - def isomorphic(&block) - yield - before_mount(&block) - end - - def set_local_var(name, object) - serialized = object.opal_serialize - if serialized - "#{name} = #{serialized}" - else - "self.class.define_method(:#{name}) "\ - "{ raise 'Attempt to access the variable #{name} "\ - 'that was defined in the spec, but its value could not be serialized '\ - "so it is undefined on the client.' }" - end - end - - def evaluate_ruby(*args, &block) - insure_page_loaded - str, opts = process_params(*args, &block) - str = add_promise_wrapper(str) - js = opal_compile(str).gsub("// Prepare super implicit arguments\n", '') - .delete("\n").gsub('(Opal);', '(Opal)') - page.execute_script("window.hyper_spec_promise_result = false; #{js}") - Timeout.timeout(Capybara.default_max_wait_time) do - loop do - break if page.evaluate_script('!!window.hyper_spec_promise_result') - - sleep 0.25 - end - end - JSON.parse(page.evaluate_script("window.hyper_spec_promise_result.$to_json()"), opts).first - end - - alias c? evaluate_ruby - - # TODO: add a to_js method. refactor evaluate_ruby and expect_evaluate ruby to use common methods - # that process the params, and produce a ruby code string, and a resulting JS string - # compile_ruby can be useful in seeing what code opal produces... - - def to_js(*args, &block) - opal_compile *process_params(*args, &block) - end - - def process_params(*args, &block) - args = ['', *args] if args[0].is_a? Hash - args = [args[0], {}, args[1] || {}] if args.length < 3 - str, opts, vars = args - vars.each do |name, value| - str = "#{name} = #{value.inspect}\n#{str}" - end - [add_opal_block(str, block), opts] - end - - def expect_evaluate_ruby(*args, &block) - expect(evaluate_ruby(*args, &block)) - end - - PRIVATE_VARIABLES = %i[ - @__inspect_output @__memoized @example @_hyperspec_private_client_code @_hyperspec_private_html_block @fixture_cache - @fixture_connections @connection_subscriber @loaded_fixtures @_hyperspec_private_client_options - b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_ - ] - - def add_promise_wrapper(str) -< 2 || stable_count_h > 2 - prev_size = curr_size - end while (Capybara::Helpers.monotonic_time - start_time) < Capybara.current_session.config.default_max_wait_time - raise Capybara::WindowError, "Window size not stable within #{Capybara.current_session.config.default_max_wait_time} seconds." - end - - def size_window(width = nil, height = nil) - # return if @window_cannot_be_resized - # original_width = evaluate_script('window.innerWidth') - # original_height = evaluate_script('window.innerHeight') - width, height = [height, width] if width == :portrait - width, height = width if width.is_a? Array - portrait = true if height == :portrait - - case width - when :small - width, height = [480, 320] - when :mobile - width, height = [640, 480] - when :tablet - width, height = [960, 640] - when :large - width, height = [1920, 6000] - when :default, nil - width, height = [1024, 768] - end - - width, height = [height, width] if portrait - - unless RSpec.configuration.debugger_width - Capybara.current_session.current_window.resize_to(1000, 500) - sleep RSpec.configuration.wait_for_initialization_time - wait_for_size(1000, 500) - inner_width = evaluate_script('window.innerWidth') - RSpec.configuration.debugger_width = 1000 - inner_width - end - Capybara.current_session.current_window - .resize_to(width + RSpec.configuration.debugger_width, height) - wait_for_size(width + RSpec.configuration.debugger_width, height) - rescue StandardError - true - end - - def attributes_on_client(model) - evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys - end - end -end diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb index e2d864716..b2a6a0b4d 100644 --- a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -4,16 +4,16 @@ module HyperSpec module ControllerHelpers # These methods are dependent on the stack being used. See the # RailsControllerHelpers module and rack.rb for two implementations. - # Each method should append the appropriate data to the @page variable + # Each method should append the appropriate code to the @page array - # return an empty 204 status - # either by setting headers or returning and appropriate response for rack. + # return an empty 204 status either by setting headers or + # returning and appropriate response for rack. def ping! raise 'must implement' end def json! - # this can be a no-op but if json included by the application, + # this can be a no-op but if json is not included by the application, # hyper-spec will fail, with an error complaining about to_json end @@ -69,7 +69,7 @@ def on_client? @render_on != :server_only end - # The controllers behavior is kept as an array of values in the ComponentTestHelpers cache + # The controllers behavior is kept as an array of values in the Controller cache # under a unique id for each test run. Grab the parameters and move them to instance vars # If this is just a ping! Then we can just exit with nil. @@ -78,7 +78,7 @@ def initialize! return if params[:id] == 'ping' key = "/#{self.class.route_root}/#{params[:id]}" - test_params = ComponentTestHelpers.cache_read(key) + test_params = Internal::Controller.cache_read(key) @component_name = test_params[0] @component_params = test_params[1] @@ -106,7 +106,7 @@ def html_block! # patch time cop and lolex so they stay in sync across the client and server TIME_COP_CLIENT_PATCH = - Opal.compile(File.read(File.expand_path('../hyper-spec/time_cop.rb', __dir__))) + + Opal.compile(File.read(File.expand_path('../hyper-spec/internal/time_cop.rb', __dir__))) + "\n#{File.read(File.expand_path('../sources/lolex.js', __dir__))}" def client_code! @@ -130,7 +130,7 @@ def go_function! # First lines displayed on the console will be the name of the spec def example_title! - title = ComponentTestHelpers.current_example_description! + title = Internal::Controller.current_example_description! @page << "" end @@ -149,11 +149,11 @@ def test return ping! unless initialize! html_block! - example_title! if ComponentTestHelpers.current_example + example_title! if Internal::Controller.current_example go_function! if on_client? style_sheet!(style_sheet_file) if style_sheet_file application!(application_file) if application_file - json! # MUST BE AFTER application_file which + json! time_cop_patch! if on_client? || Lolex.initialized? client_code! if on_client? mount_component! if @component_name diff --git a/ruby/hyper-spec/lib/hyper-spec/expectations.rb b/ruby/hyper-spec/lib/hyper-spec/expectations.rb index 30dfc3d37..4b2a033c6 100644 --- a/ruby/hyper-spec/lib/hyper-spec/expectations.rb +++ b/ruby/hyper-spec/lib/hyper-spec/expectations.rb @@ -4,7 +4,7 @@ module Expectations class ExpectationTarget; end module HyperSpecInstanceMethods def self.included(base) - base.include HyperSpec::ComponentTestHelpers + base.include HyperSpec::Helpers end def to_on_client(matcher, message = nil, &block) diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb new file mode 100644 index 000000000..0570bc9a2 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -0,0 +1,242 @@ +module HyperSpec + module Helpers + include Internal::ClientExecution + include Internal::Controller + include Internal::ComponentMount + include Internal::WindowSizing + + ## + # Mount a component on a page, with a full execution environment + # i.e. `mount('MyComponent')` will mount MyComponent on the page. + + # The params argument is a hash of parameters to be passed to the + # component. + # i.e. `mount('MyComponent', title: 'hello')` + + # The options parameters can set things like: + # + controller: the controller class, defaults to HyperSpecTestController + # + no_wait: do not wait for any JS to finish executing before proceeding with the spec + # + render_on: :client_only (default), :client_and_server, or :server_only + # + style_sheet: style sheet file defaults to 'application' + # + javascript: javascript file defaults to 'application' + # + layout: if provided will use the specified layout otherwise no layout is used + # Note that if specifying options the params will have to inclosed in their + # own hash. + # i.e. `mount('MyComponent', { title: 'hello' }, render_on: :server_only)` + # The options can be specified globally using the client_options method (see below.) + + # You may provide a block to mount. This block will be executed on the client + # before mounting the component. This is useful for setting up test + # components or modifying a components behavior. + # i.e. + # ```ruby + # mount('MyComponent', title: 'hello') do + # # this line will be printed on the client console + # puts "I'm about to mount my component!" + # end + # ``` + def mount(component_name = nil, params = nil, opts = {}, &block) + unless params + params = opts + opts = {} + end + internal_mount(component_name, params, client_options(opts), &block) + end + + ## + # The following methods retrieve callback and event responses from + # the mounted components. The history methods contain the array of all + # responses, while last_... returns the last response. + # i.e. event_history_for(:save) would return any save events + # that the component has raised. + + %i[ + callback_history_for last_callback_for clear_callback_history_for + event_history_for last_event_for clear_event_history_for + ].each do |method| + define_method(method) do |event_name| + evaluate_ruby( + "Hyperstack::Internal::Component::TopLevelRailsComponent.#{method}('#{event_name}')" + ) + end + end + + ## + # Define a code block to be prefixed to the mount code. + # Useful in before(:each) blocks. + + # In legacy code this was called `on_client`. To get the legacy + # behavior alias on_client before_mount + # but be aware that on_client is now by default the method + # for executing a block of code on the client which was called + # evaluate_ruby + + def before_mount(&block) + @_hyperspec_private_client_code = + "#{@_hyperspec_private_client_code}"\ + "#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}\n" + end + + # Execute the block both on the client and on the server. Useful + # for mocking isomorphic classes such as ActiveRecord models. + + def isomorphic(&block) + yield + before_mount(&block) + end + + # Allows options to the mount method to be specified globally + + def client_option(opts = {}) + @_hyperspec_private_client_options ||= {} + @_hyperspec_private_client_options.merge! opts + end + + alias client_options client_option + + ## + # shorthand for mount with no params (which will effectively reload the page.) + # also aliased as reload_page + def load_page + mount + end + + alias reload_page load_page + + ## + # evaluate a block (or string) on the client + # on_client(, , &block) + # + # normal use is to pass a block that will be compiled to the client + # but if the ruby code can be supplied as a string in the first arg. + + # opts are passed on to JSON.parse when retrieving the result + # from the client. + + # vars is a hash of name: value pairs. Each name will be initialized + # as a local variable on the client. + + # example: on_client(x: 12) { x * x } => 144 + + # in legacy code on_client was called before_mount + # to get legacy on_client behavior you can alias + # on_client before_mount + + alias on_client internal_evaluate_ruby + + # same signature as on_client, but just returns the compiled + # js code. Useful for debugging suspected issues with the + # Opal compiler, etc. + + def to_js(*args, &block) + opal_compile(*process_params(*args, &block)) + end + + # legacy methods for backwards compatibility + # these may be removed in a future version + + def expect_evaluate_ruby(*args, &block) + expect(evaluate_ruby(*args, &block)) + end + + alias evaluate_ruby internal_evaluate_ruby + alias evaluate_promise evaluate_ruby + alias expect_promise expect_evaluate_ruby + + def run_on_client(&block) + script = opal_compile(Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last)) + page.execute_script(script) + end + + def insert_html(str) + @_hyperspec_private_html_block = "#{@_hyperspec_private_html_block}\n#{str}" + end + + def ppr(str) + js = Opal.hyperspec_compile(str) + execute_script("console.log(#{js})") + end + + def debugger + `debugger` + nil + end + + def add_class(class_name, style) + @_hyperspec_private_client_code = + "#{@_hyperspec_private_client_code}ComponentHelpers.add_class('#{class_name}', #{style})\n" + end + + def attributes_on_client(model) + evaluate_ruby("#{model.class.name}.find(#{model.id}).attributes").symbolize_keys + end + + ### --- Debugging Helpers ---- + + def pause(message = nil) + if message + puts message + page.evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" + end + + page.evaluate_script('window.hyper_spec_waiting_for_go = true') + + loop do + sleep 0.25 + break unless page.evaluate_script('window.hyper_spec_waiting_for_go') + end + end + + def open_in_chrome + # if ['linux', 'freebsd'].include?(`uname`.downcase) + # `google-chrome http://#{page.server.host}:#{page.server.port}#{page.current_path}` + # else + `open http://#{page.server.host}:#{page.server.port}#{page.current_path}` + # end + + loop do + sleep 1.hour + end + end + + # short hand for use in pry sessions + alias c? internal_evaluate_ruby + + def size_window(width = nil, height = nil) + # return if @window_cannot_be_resized + # original_width = evaluate_script('window.innerWidth') + # original_height = evaluate_script('window.innerHeight') + width, height = [height, width] if width == :portrait + width, height = width if width.is_a? Array + portrait = true if height == :portrait + + case width + when :small + width, height = [480, 320] + when :mobile + width, height = [640, 480] + when :tablet + width, height = [960, 640] + when :large + width, height = [1920, 6000] + when :default, nil + width, height = [1024, 768] + end + + width, height = [height, width] if portrait + + unless RSpec.configuration.debugger_width + Capybara.current_session.current_window.resize_to(1000, 500) + sleep RSpec.configuration.wait_for_initialization_time + wait_for_size(1000, 500) + inner_width = evaluate_script('window.innerWidth') + RSpec.configuration.debugger_width = 1000 - inner_width + end + Capybara.current_session.current_window + .resize_to(width + RSpec.configuration.debugger_width, height) + wait_for_size(width + RSpec.configuration.debugger_width, height) + rescue StandardError + true + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb new file mode 100644 index 000000000..66c0004c4 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb @@ -0,0 +1,133 @@ +module HyperSpec + module Internal + module ClientExecution + def internal_evaluate_ruby(*args, &block) + insure_page_loaded + add_promise_execute_and_wait(*process_params(*args, &block)) + end + + private + + PRIVATE_VARIABLES = %i[ + @__inspect_output @__memoized @example @_hyperspec_private_client_code + @_hyperspec_private_html_block @fixture_cache + @fixture_connections @connection_subscriber @loaded_fixtures + @_hyperspec_private_client_options + b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_ + ] + + def add_locals(in_str, block) + b = block.binding + + memoized = b.eval('__memoized').instance_variable_get(:@memoized) + in_str = memoized.inject(in_str) do |str, pair| + "#{str}\n#{set_local_var(pair.first, pair.last)}" + end if memoized + + in_str = b.local_variables.inject(in_str) do |str, var| + next str if PRIVATE_VARIABLES.include? var + + "#{str}\n#{set_local_var(var, b.local_variable_get(var))}" + end + + in_str = b.eval('instance_variables').inject(in_str) do |str, var| + next str if PRIVATE_VARIABLES.include? var + + "#{str}\n#{set_local_var(var, b.eval("instance_variable_get('#{var}')"))}" + end + in_str + end + + def add_opal_block(str, block) + return str unless block + + source = block.source + ast = Parser::CurrentRuby.parse(source) + ast = find_block(ast) + raise "could not find block within source: #{block.source}" unless ast + + "#{add_locals(str, block)}\n#{Unparser.unparse ast.children.last}" + end + + def add_promise_execute_and_wait(str, opts) + js = opal_compile(add_promise_wrapper(str)) + page.execute_script("window.hyper_spec_promise_result = false; #{js}") + Timeout.timeout(Capybara.default_max_wait_time) do + loop do + break if page.evaluate_script('!!window.hyper_spec_promise_result') + + sleep 0.25 + end + end + JSON.parse(page.evaluate_script('window.hyper_spec_promise_result.$to_json()'), opts).first + end + + def add_promise_wrapper(str) + <<~RUBY + (#{str}).tap do |r| + if defined?(Promise) && r.is_a?(Promise) + r.then { |args| `window.hyper_spec_promise_result = [args]` } + else + #after(0) do + #puts "setting window.hyper_spec_promise_result = [\#{r}]" + `window.hyper_spec_promise_result = [r]` + #end + end + end + RUBY + end + + def find_block(node) + # find a block with the ast tree. + + return false unless node.class == Parser::AST::Node + return node if the_node_you_are_looking_for?(node) + + node.children.each do |child| + found = find_block(child) + return found if found + end + false + end + + def process_params(*args, &block) + args = ['', *args] if args[0].is_a? Hash + args = [args[0], {}, args[1] || {}] if args.length < 3 + str, opts, vars = args + vars.each do |name, value| + str = "#{name} = #{value.inspect}\n#{str}" + end + [add_opal_block(str, block), opts] + end + + def the_node_you_are_looking_for?(node) + # we could also check that the block is going to the right method + # respond_to?(node.children.first.children[1]) && + # method(node.children.first.children[1]) == method(:evaluate_ruby) + # however that does not work for expect { ... }.on_client_to ... + # because now the block is being sent to expect... so we could + # check the above OR node.children.first.children[1] == :expect + # but what if there are two blocks? on and on... + node.type == :block && + node.children.first.class == Parser::AST::Node && + node.children.first.type == :send + end + + def set_local_var(name, object) + serialized = object.opal_serialize + if serialized + "#{name} = #{serialized}" + else + "self.class.define_method(:#{name}) "\ + "{ raise 'Attempt to access the variable #{name} "\ + 'that was defined in the spec, but its value could not be serialized '\ + "so it is undefined on the client.' }" + end + end + + def opal_compile(str, *) + Opal.hyperspec_compile(str) + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb new file mode 100644 index 000000000..f57524932 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb @@ -0,0 +1,122 @@ +module HyperSpec + module Internal + module ComponentMount + private + + TEST_CODE_KEY = 'hyper_spec_prerender_test_code.js'.freeze + + # rubocop:disable Metrics/MethodLength + def add_block_with_helpers(component_name, opts, block) + return unless block || @_hyperspec_private_client_code || component_name.nil? + + block_with_helpers = <<-RUBY + module ComponentHelpers + def self.js_eval(s) + `eval(s)` + end + def self.dasherize(s) + res = %x{ + s.replace(/[-_\\s]+/g, '-') + .replace(/([A-Z\\d]+)([A-Z][a-z])/g, '$1-$2') + .replace(/([a-z\\d])([A-Z])/g, '$1-$2') + .toLowerCase() + } + res + end + def self.add_class(class_name, styles={}) + style = styles.collect { |attr, value| "\#{dasherize(attr)}:\#{value}" }.join("; ") + cs = class_name.to_s + %x{ + var style_el = document.createElement("style"); + var css = "." + cs + " { " + style + " }"; + style_el.type = "text/css"; + if (style_el.styleSheet){ + style_el.styleSheet.cssText = css; + } else { + style_el.appendChild(document.createTextNode(css)); + } + document.head.appendChild(style_el); + } + end + end + #{test_dummy} + #{@_hyperspec_private_client_code} + #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} + RUBY + @_hyperspec_private_client_code = nil + opts[:code] = Opal.hyperspec_compile(block_with_helpers) + end + # rubocop:enable Metrics/MethodLength + + def build_test_url_for(controller = nil, ping = nil) + id = ping ? 'ping' : Controller.test_id + "/#{route_root_for(controller)}/#{id}" + end + + def insure_page_loaded(only_if_code_or_html_exists = nil) + return if only_if_code_or_html_exists && !@_hyperspec_private_client_code && !@_hyperspec_private_html_block + # if we are not resetting between examples, or think its mounted + # then look for Opal, but if we can't find it, then ping to clear and try again + if !HyperSpec.reset_between_examples? || page.instance_variable_get('@hyper_spec_mounted') + r = evaluate_script('Opal && true') rescue nil + return if r + page.visit build_test_url_for(nil, true) rescue nil + end + load_page + end + + def internal_mount(component_name, params, opts, &block) + # TODO: refactor this + test_url = build_test_url_for(opts.delete(:controller)) + add_block_with_helpers(component_name, opts, block) + send_params_to_controller_via_cache(test_url, component_name, params, opts) + setup_prerendering(opts) + page.instance_variable_set('@hyper_spec_mounted', false) + visit test_url + wait_for_ajax unless opts[:no_wait] + page.instance_variable_set('@hyper_spec_mounted', true) + Lolex.init(self, client_options[:time_zone], client_options[:clock_resolution]) + end + + def prerendering?(opts) + %i[both server_only].include?(opts[:render_on]) + end + + def send_params_to_controller_via_cache(test_url, component_name, params, opts) + component_name ||= 'Hyperstack::Internal::Component::TestDummy' if test_dummy + Controller.cache_write( + test_url, + [component_name, params, @_hyperspec_private_html_block, opts] + ) + @_hyperspec_private_html_block = nil + end + + def setup_prerendering(opts) + return unless defined?(::Hyperstack::Component) && prerendering?(opts) + + @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] + ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + if opts[:code].blank? + Controller.cache_delete(TEST_CODE_KEY) + else + Controller.cache_write(TEST_CODE_KEY, opts[:code]) + @@original_server_render_files += [TEST_CODE_KEY] + end + ::React::ServerRendering.reset_pool + # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher + # doesnt look for cache changes + end + + def test_dummy + return unless defined? ::Hyperstack::Component + + <<-RUBY + class Hyperstack::Internal::Component::TestDummy + include Hyperstack::Component + render {} + end + RUBY + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/controller.rb b/ruby/hyper-spec/lib/hyper-spec/internal/controller.rb new file mode 100644 index 000000000..04de4251d --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/controller.rb @@ -0,0 +1,70 @@ +module HyperSpec + module Internal + module Controller + class << self + attr_accessor :current_example + attr_accessor :description_displayed + + def test_id + @_hyperspec_private_test_id ||= 0 + @_hyperspec_private_test_id += 1 + end + + include ActionView::Helpers::JavaScriptHelper + + def current_example_description! + title = "#{title}...continued." if description_displayed + self.description_displayed = true + "#{escape_javascript(current_example.description)}#{title}" + end + + def file_cache + @file_cache ||= FileCache.new('cache', '/tmp/hyper-spec-caches', 30, 3) + end + + def cache_read(key) + file_cache.get(key) + end + + def cache_write(key, value) + file_cache.set(key, value) + end + + def cache_delete(key) + file_cache.delete(key) + rescue StandardError + nil + end + end + + # By default we assume we are operating in a Rails environment and will + # hook in using a rails controller. To override this define the + # HyperSpecController class in your spec helper. See the rack.rb file + # for an example of how to do this. + + def hyper_spec_test_controller + return ::HyperSpecTestController if defined?(::HyperSpecTestController) + + base = if defined? ApplicationController + Class.new ApplicationController + elsif defined? ::ActionController::Base + Class.new ::ActionController::Base + else + raise "Unless using Rails you must define the HyperSpecTestController\n"\ + 'For rack apps try requiring hyper-spec/rack.' + end + Object.const_set('HyperSpecTestController', base) + end + + # First insure we have a controller, then make sure it responds to the test method + # if not, then add the rails specific controller methods. The RailsControllerHelpers + # module will automatically add a top level route back to the controller. + + def route_root_for(controller) + controller ||= hyper_spec_test_controller + controller.include RailsControllerHelpers unless controller.method_defined?(:test) + controller.route_root + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/patches.rb b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb similarity index 61% rename from ruby/hyper-spec/lib/hyper-spec/patches.rb rename to ruby/hyper-spec/lib/hyper-spec/internal/patches.rb index e88cc5111..eaebd2b90 100644 --- a/ruby/hyper-spec/lib/hyper-spec/patches.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb @@ -1,9 +1,25 @@ +module Opal + # strips off stuff that confuses things when transmitting to the client + # and prints offending code if it can't be compiled + def self.hyperspec_compile(str) + compile(str).gsub("// Prepare super implicit arguments\n", '') + .delete("\n").gsub('(Opal);', '(Opal)') + # rubocop:disable Lint/RescueException + # we are going to reraise it anyway, so its fine to catch EVERYTHING! + rescue Exception => e + puts "puts could not compile: \n\n#{str}\n\n" + raise e + end + # rubocop:enable Lint/RescueException +end + module Unparser class Emitter # Emitter for send class Send < self def local_variable_clash? - selector =~ /^[A-Z]/ || local_variable_scope.local_variable_defined_for_node?(node, selector) + selector =~ /^[A-Z]/ || + local_variable_scope.local_variable_defined_for_node?(node, selector) end end end @@ -14,7 +30,7 @@ class << self alias original_lines_for_before_hyper_spec lines_for alias original_source_helper_before_hyper_spec source_helper - def source_helper(source_location, name=nil) + def source_helper(source_location, name = nil) source_location[1] = 1 if source_location[0] == '(pry)' original_source_helper_before_hyper_spec source_location, name end @@ -37,7 +53,7 @@ def opal_serialize class Hash def opal_serialize - "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}"}.join(', ')}}" + "{#{collect { |k, v| "#{k.opal_serialize} => #{v.opal_serialize}" }.join(', ')}}" end end @@ -53,6 +69,7 @@ def opal_serialize end end +# rubocop:disable Lint/UnifiedInteger - patch for ruby prior to 2.4 if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0') [Bignum, Fixnum].each do |klass| klass.send(:define_method, :opal_serialize) do @@ -60,6 +77,7 @@ def opal_serialize end end end +# rubocop:enable Lint/UnifiedInteger class Time def to_opal_expression diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/rails_controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/internal/rails_controller_helpers.rb new file mode 100644 index 000000000..e36089858 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/rails_controller_helpers.rb @@ -0,0 +1,50 @@ +module HyperSpec + module Internal + module RailsControllerHelpers + def self.included(base) + base.include ControllerHelpers + base.include Helpers + routes = ::Rails.application.routes + routes.disable_clear_and_finalize = true + routes.clear! + routes.draw { get "/#{base.route_root}/:id", to: "#{base.route_root}#test" } + ::Rails.application.routes_reloader.paths.each { |path| load(path) } + routes.finalize! + ActiveSupport.on_load(:action_controller) { routes.finalize! } + ensure + routes.disable_clear_and_finalize = false + end + + module Helpers + def ping! + head(:no_content) + nil + end + + def mount_component! + @page << '<%= react_component @component_name, @component_params, '\ + "{ prerender: #{@render_on != :client_only} } %>" + end + + def application!(file) + @page << "<%= javascript_include_tag '#{file}' %>" + end + + def style_sheet!(file) + @page << "<%= stylesheet_link_tag '#{file}' %>" + end + + def deliver! + @render_params[:inline] = @page + response.headers['Cache-Control'] = 'max-age=120' + response.headers['X-Tracking-ID'] = '123456' + render @render_params + end + + def server_only? + @render_on == :server_only + end + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/time_cop.rb b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb similarity index 96% rename from ruby/hyper-spec/lib/hyper-spec/time_cop.rb rename to ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb index 0f4f9f521..faf8d8de8 100644 --- a/ruby/hyper-spec/lib/hyper-spec/time_cop.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb @@ -148,7 +148,7 @@ def pending_evaluations def evaluate_ruby(&block) if @capybara_page - @capybara_page.evaluate_ruby(yield) + @capybara_page.on_client(yield) else pending_evaluations << block end @@ -156,9 +156,7 @@ def evaluate_ruby(&block) def run_pending_evaluations return if pending_evaluations.empty? - puts "time cop evaluating ruby" - @capybara_page.evaluate_ruby(pending_evaluations.collect(&:call).join("\n")) - puts "done" + @capybara_page.on_client(pending_evaluations.collect(&:call).join("\n")) @pending_evaluations ||= [] end end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb new file mode 100644 index 000000000..5e7943cc0 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb @@ -0,0 +1,23 @@ +module HyperSpec + module Internal + module WindowSizing + def wait_for_size(width, height) + start_time = Capybara::Helpers.monotonic_time + stable_count_w = 0 + stable_count_h = 0 + prev_size = [0, 0] + begin + sleep 0.05 + curr_size = Capybara.current_session.current_window.size + return if [width, height] == curr_size + # some maximum or minimum is reached and size doesnt change anymore + stable_count_w += 1 if prev_size[0] == curr_size[0] + stable_count_h += 1 if prev_size[1] == curr_size[1] + return if stable_count_w > 2 || stable_count_h > 2 + prev_size = curr_size + end while (Capybara::Helpers.monotonic_time - start_time) < Capybara.current_session.config.default_max_wait_time + raise Capybara::WindowError, "Window size not stable within #{Capybara.current_session.config.default_max_wait_time} seconds." + end + end + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb deleted file mode 100644 index af953f5c0..000000000 --- a/ruby/hyper-spec/lib/hyper-spec/rails_controller_helpers.rb +++ /dev/null @@ -1,48 +0,0 @@ -module HyperSpec - module RailsControllerHelpers - def self.included(base) - base.include ControllerHelpers - base.include Helpers - routes = ::Rails.application.routes - routes.disable_clear_and_finalize = true - routes.clear! - routes.draw { get "/#{base.route_root}/:id", to: "#{base.route_root}#test" } - ::Rails.application.routes_reloader.paths.each { |path| load(path) } - routes.finalize! - ActiveSupport.on_load(:action_controller) { routes.finalize! } - ensure - routes.disable_clear_and_finalize = false - end - - module Helpers - def ping! - head(:no_content) - nil - end - - def mount_component! - @page << '<%= react_component @component_name, @component_params, '\ - "{ prerender: #{@render_on != :client_only} } %>" - end - - def application!(file) - @page << "<%= javascript_include_tag '#{file}' %>" - end - - def style_sheet!(file) - @page << "<%= stylesheet_link_tag '#{file}' %>" - end - - def deliver! - @render_params[:inline] = @page - response.headers['Cache-Control'] = 'max-age=120' - response.headers['X-Tracking-ID'] = '123456' - render @render_params - end - - def server_only? - @render_on == :server_only - end - end - end -end diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 542f832e1..b2f7744cb 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -2,7 +2,7 @@ describe 'hyper-spec', js: true do - after(:each) { |e| binding.pry if e.exception } + # after(:each) { |e| binding.pry if e.exception } it 'can visit a page' do visit 'test' @@ -205,9 +205,9 @@ class StyledDiv start_time = Time.now sleep 1 # sleep is still in "real time" but Time will move 60 times faster expect(start_time).to be_within(1).of(Time.now-1.minute) - expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(2).of(Time.now.to_i) + expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(3).of(Time.now.to_i) end - expect(evaluate_ruby('Time.now.to_i')).to be_within(2).of(Time.now.to_i+@sync_gap) + expect(evaluate_ruby('Time.now.to_i')).to be_within(3).of(Time.now.to_i+@sync_gap) end it "will advance time along with time cop freezing" do @@ -216,7 +216,7 @@ class StyledDiv Timecop.freeze Time.now-2.years expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(1).of(Time.now.to_i) Timecop.return - expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(2).of(Time.now.to_i+@sync_gap) + expect(evaluate_ruby('puts ""; Time.now.to_i')).to be_within(3).of(Time.now.to_i+@sync_gap) end it "can temporarily return to true time" do From 840af31fc858f4d349a293a6bfb4f4d177edc9f8 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 4 Feb 2021 19:47:36 -0500 Subject: [PATCH 080/307] updated per latest changes in hyper-spec --- ruby/hyper-component/spec/spec_helper.rb | 2 +- ruby/hyper-i18n/spec/spec_helper.rb | 2 +- ruby/hyper-model/spec/spec_helper.rb | 2 +- ruby/hyper-operation/spec/spec_helper.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-component/spec/spec_helper.rb b/ruby/hyper-component/spec/spec_helper.rb index 711eacab6..47c233658 100644 --- a/ruby/hyper-component/spec/spec_helper.rb +++ b/ruby/hyper-component/spec/spec_helper.rb @@ -65,7 +65,7 @@ class JavaScriptError < StandardError; end end end - HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount + HyperSpec::Helpers.alias_method :on_client, :before_mount end # Stubbing the React calls so we can test outside of Opal diff --git a/ruby/hyper-i18n/spec/spec_helper.rb b/ruby/hyper-i18n/spec/spec_helper.rb index cb505b28b..a5a678924 100644 --- a/ruby/hyper-i18n/spec/spec_helper.rb +++ b/ruby/hyper-i18n/spec/spec_helper.rb @@ -28,5 +28,5 @@ end # Use legacy hyper-spec on_client behavior - HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount + HyperSpec::Helpers.alias_method :on_client, :before_mount end diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index fe04f7b01..1cf662002 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -340,7 +340,7 @@ class JavaScriptError < StandardError; end # end # Use legacy hyper-spec on_client behavior - HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount + HyperSpec::Helpers.alias_method :on_client, :before_mount end FactoryBot.define do diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index e2260d48d..b7432f835 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -133,7 +133,7 @@ def self.on_server? # end # Use legacy hyper-spec on_client behavior - HyperSpec::ComponentTestHelpers.alias_method :on_client, :before_mount + HyperSpec::Helpers.alias_method :on_client, :before_mount end module HyperSpec From 8006a3a7b71155411c109f93dd89fcb95f09162e Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 10 Feb 2021 10:56:01 -0500 Subject: [PATCH 081/307] wip - window sizing not reliable in chrome yet --- ruby/hyper-spec/lib/hyper-spec/helpers.rb | 46 +++-------- .../lib/hyper-spec/internal/window_sizing.rb | 78 ++++++++++++++++--- ruby/hyper-spec/spec/hyper_spec.rb | 1 + ruby/hyper-spec/spec/spec_helper.rb | 8 +- 4 files changed, 80 insertions(+), 53 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index 0570bc9a2..162d6e552 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -124,6 +124,15 @@ def load_page alias on_client internal_evaluate_ruby + # attempt to set the window to a particular size + + def size_window(width = nil, height = nil) + hs_internal_resize_to(*adjust_size(width, height)) + rescue StandardError => e + binding.pry + true + end + # same signature as on_client, but just returns the compiled # js code. Useful for debugging suspected issues with the # Opal compiler, etc. @@ -201,42 +210,5 @@ def open_in_chrome # short hand for use in pry sessions alias c? internal_evaluate_ruby - - def size_window(width = nil, height = nil) - # return if @window_cannot_be_resized - # original_width = evaluate_script('window.innerWidth') - # original_height = evaluate_script('window.innerHeight') - width, height = [height, width] if width == :portrait - width, height = width if width.is_a? Array - portrait = true if height == :portrait - - case width - when :small - width, height = [480, 320] - when :mobile - width, height = [640, 480] - when :tablet - width, height = [960, 640] - when :large - width, height = [1920, 6000] - when :default, nil - width, height = [1024, 768] - end - - width, height = [height, width] if portrait - - unless RSpec.configuration.debugger_width - Capybara.current_session.current_window.resize_to(1000, 500) - sleep RSpec.configuration.wait_for_initialization_time - wait_for_size(1000, 500) - inner_width = evaluate_script('window.innerWidth') - RSpec.configuration.debugger_width = 1000 - inner_width - end - Capybara.current_session.current_window - .resize_to(width + RSpec.configuration.debugger_width, height) - wait_for_size(width + RSpec.configuration.debugger_width, height) - rescue StandardError - true - end end end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb index 5e7943cc0..29755a2ed 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb @@ -1,22 +1,76 @@ module HyperSpec module Internal module WindowSizing + + private + + STD_SIZES = { + small: [480, 320], + mobile: [640, 480], + tablet: [960, 640], + large: [1920, 6000], + default: [1024, 768] + } + + def adjust_size(width, height) + width, height = [height, width] if width == :portrait + width, height = width if width.is_a? Array + portrait = true if height == :portrait + width ||= :default + width, height = STD_SIZES[width] if STD_SIZES[width] + width, height = [height, width] if portrait + [width + debugger_width, height] + end + + def debugger_width + RSpec.configuration.debugger_width ||= begin + hs_internal_resize_to(1000, 500) do + sleep RSpec.configuration.wait_for_initialization_time + end + inner_width = evaluate_script('window.innerWidth') + 1000 - inner_width + end + puts "debugger_width: #{RSpec.configuration.debugger_width}" + RSpec.configuration.debugger_width + end + + def hs_internal_resize_to(width, height) + puts "hs_internal_resize_to(#{width}, #{height})" + Capybara.current_session.current_window.resize_to(width, height) + yield if block_given? + wait_for_size(width, height) + end + def wait_for_size(width, height) - start_time = Capybara::Helpers.monotonic_time - stable_count_w = 0 - stable_count_h = 0 + @start_time = Capybara::Helpers.monotonic_time + @stable_count_w = @stable_count_h = 0 prev_size = [0, 0] - begin + loop do sleep 0.05 - curr_size = Capybara.current_session.current_window.size - return if [width, height] == curr_size - # some maximum or minimum is reached and size doesnt change anymore - stable_count_w += 1 if prev_size[0] == curr_size[0] - stable_count_h += 1 if prev_size[1] == curr_size[1] - return if stable_count_w > 2 || stable_count_h > 2 + curr_size = evaluate_script('[window.innerWidth, window.innerHeight]') + puts "waiting for size: #{width}, #{height} currently: #{curr_size}" + + return true if curr_size == [width, height] || stalled?(prev_size, curr_size) + prev_size = curr_size - end while (Capybara::Helpers.monotonic_time - start_time) < Capybara.current_session.config.default_max_wait_time - raise Capybara::WindowError, "Window size not stable within #{Capybara.current_session.config.default_max_wait_time} seconds." + check_time! + end + end + + def check_time! + if (Capybara::Helpers.monotonic_time - @start_time) > + Capybara.current_session.config.default_max_wait_time + raise Capybara::WindowError, + 'Window size not stable within '\ + "#{Capybara.current_session.config.default_max_wait_time} seconds." + end + end + + def stalled?(prev_size, curr_size) + # some maximum or minimum is reached and size doesn't change anymore + @stable_count_w += 1 if prev_size[0] == curr_size[0] + @stable_count_h += 1 if prev_size[1] == curr_size[1] + @stable_count_w > 4 || @stable_count_h > 4 end end end diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index b2f7744cb..934eb4ef1 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -317,6 +317,7 @@ class StyledDiv it "the default portrait size" do size_window(:portrait) + binding.pry expect(dims).to eq(adjusted(768, 1024)) end diff --git a/ruby/hyper-spec/spec/spec_helper.rb b/ruby/hyper-spec/spec/spec_helper.rb index 413bbed44..3789c40a4 100644 --- a/ruby/hyper-spec/spec/spec_helper.rb +++ b/ruby/hyper-spec/spec/spec_helper.rb @@ -17,13 +17,13 @@ def computed_style(selector, prop) end def calculate_window_restrictions return if @min_width - size_window(100,100) + size_window(100, 100) @min_width = width - size_window(500,500) - @height_adjust = 500-height + size_window(500, 500) + @height_adjust = 500 - height size_window(6000, 6000) @max_width = width - @max_height = height + @max_height = height-@height_adjust end def height evaluate_script('window.innerHeight') From b70736c36af9e3251b623dc231d0265944460d94 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 13 Feb 2021 17:42:02 -0500 Subject: [PATCH 082/307] remove delay and interval file --- ruby/hyper-operation/lib/hyper-operation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-operation/lib/hyper-operation.rb b/ruby/hyper-operation/lib/hyper-operation.rb index e01d34d29..bb41ec4cf 100644 --- a/ruby/hyper-operation/lib/hyper-operation.rb +++ b/ruby/hyper-operation/lib/hyper-operation.rb @@ -45,7 +45,7 @@ def titleize require 'hyper-operation/transport/client_drivers' require 'hyper-operation/transport/acting_user' require 'opal-activesupport' - require 'hyper-operation/delay_and_interval' + # require 'hyper-operation/delay_and_interval' require 'hyper-operation/exception' require 'hyper-operation/promise' require 'hyper-operation/railway' From 38412b7e78060ddc6ad85ab1c42e41aded2908c3 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 13 Feb 2021 18:45:53 -0500 Subject: [PATCH 083/307] window sizing now working --- ruby/hyper-spec/lib/hyper-spec/helpers.rb | 5 ++--- .../lib/hyper-spec/internal/window_sizing.rb | 6 +----- ruby/hyper-spec/spec/hyper_spec.rb | 1 - ruby/hyper-spec/spec/spec_helper.rb | 14 ++++++++++++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index 162d6e552..19bb05559 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -127,9 +127,8 @@ def load_page # attempt to set the window to a particular size def size_window(width = nil, height = nil) - hs_internal_resize_to(*adjust_size(width, height)) - rescue StandardError => e - binding.pry + hs_internal_resize_to(*determine_size(width, height)) + rescue StandardError true end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb index 29755a2ed..042f5be51 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/window_sizing.rb @@ -1,7 +1,6 @@ module HyperSpec module Internal module WindowSizing - private STD_SIZES = { @@ -12,7 +11,7 @@ module WindowSizing default: [1024, 768] } - def adjust_size(width, height) + def determine_size(width, height) width, height = [height, width] if width == :portrait width, height = width if width.is_a? Array portrait = true if height == :portrait @@ -30,12 +29,10 @@ def debugger_width inner_width = evaluate_script('window.innerWidth') 1000 - inner_width end - puts "debugger_width: #{RSpec.configuration.debugger_width}" RSpec.configuration.debugger_width end def hs_internal_resize_to(width, height) - puts "hs_internal_resize_to(#{width}, #{height})" Capybara.current_session.current_window.resize_to(width, height) yield if block_given? wait_for_size(width, height) @@ -48,7 +45,6 @@ def wait_for_size(width, height) loop do sleep 0.05 curr_size = evaluate_script('[window.innerWidth, window.innerHeight]') - puts "waiting for size: #{width}, #{height} currently: #{curr_size}" return true if curr_size == [width, height] || stalled?(prev_size, curr_size) diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 934eb4ef1..b2f7744cb 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -317,7 +317,6 @@ class StyledDiv it "the default portrait size" do size_window(:portrait) - binding.pry expect(dims).to eq(adjusted(768, 1024)) end diff --git a/ruby/hyper-spec/spec/spec_helper.rb b/ruby/hyper-spec/spec/spec_helper.rb index 3789c40a4..2ed740a54 100644 --- a/ruby/hyper-spec/spec/spec_helper.rb +++ b/ruby/hyper-spec/spec/spec_helper.rb @@ -15,27 +15,37 @@ def computed_style(selector, prop) "window.getComputedStyle(document.querySelector('#{selector}'))['#{prop}']" ) end + def calculate_window_restrictions return if @min_width + size_window(100, 100) @min_width = width + @min_height = height size_window(500, 500) @height_adjust = 500 - height size_window(6000, 6000) @max_width = width - @max_height = height-@height_adjust + @max_height = height end + def height evaluate_script('window.innerHeight') end + def width evaluate_script('window.innerWidth') end + def dims [width, height] end + def adjusted(width, height) - [[@max_width, [width, @min_width].max].min, [@max_height, height-@height_adjust].min] + [ + [@max_width, [width, @min_width].max].min, + [@max_height, [height - @height_adjust, @min_height].max].min + ] end end From 711aa5a40e3d136cc378bbb7755cbe5368c6fcc5 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 13 Feb 2021 22:44:23 -0500 Subject: [PATCH 084/307] changed how we access after and every on server --- ruby/hyper-operation/lib/hyper-operation.rb | 3 ++- .../lib/hyper-operation/async_sleep.rb | 23 +++++++++++++++++++ .../lib/hyper-operation/delay_and_interval.rb | 9 -------- .../spec/hyper-operation/basics_spec.rb | 1 + 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 ruby/hyper-operation/lib/hyper-operation/async_sleep.rb delete mode 100644 ruby/hyper-operation/lib/hyper-operation/delay_and_interval.rb diff --git a/ruby/hyper-operation/lib/hyper-operation.rb b/ruby/hyper-operation/lib/hyper-operation.rb index bb41ec4cf..da470051f 100644 --- a/ruby/hyper-operation/lib/hyper-operation.rb +++ b/ruby/hyper-operation/lib/hyper-operation.rb @@ -28,6 +28,7 @@ def titleize require 'hyper-operation/railway/validations' require 'hyper-operation/server_op' require 'hyper-operation/boot' + require 'hyper-operation/async_sleep' else require 'tty-table' require 'hyperstack-config' @@ -45,7 +46,7 @@ def titleize require 'hyper-operation/transport/client_drivers' require 'hyper-operation/transport/acting_user' require 'opal-activesupport' - # require 'hyper-operation/delay_and_interval' + require 'hyper-operation/async_sleep' require 'hyper-operation/exception' require 'hyper-operation/promise' require 'hyper-operation/railway' diff --git a/ruby/hyper-operation/lib/hyper-operation/async_sleep.rb b/ruby/hyper-operation/lib/hyper-operation/async_sleep.rb new file mode 100644 index 000000000..800b5f20e --- /dev/null +++ b/ruby/hyper-operation/lib/hyper-operation/async_sleep.rb @@ -0,0 +1,23 @@ +module Hyperstack + module AsyncSleep + if RUBY_ENGINE == 'opal' + def self.every(*args, &block) + every(*args, &block) + end + + def self.after(*args, &block) + after(*args, &block) + end + else + extend self + + def every(time, &block) + Thread.new { loop { sleep time; block.call } } + end + + def after(time, &block) + Thread.new { sleep time; block.call } + end + end + end +end diff --git a/ruby/hyper-operation/lib/hyper-operation/delay_and_interval.rb b/ruby/hyper-operation/lib/hyper-operation/delay_and_interval.rb deleted file mode 100644 index 3f1f11424..000000000 --- a/ruby/hyper-operation/lib/hyper-operation/delay_and_interval.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Kernel - def every(time, &block) - Thread.new { loop { sleep time; block.call }} - end - - def after(time, &block) - Thread.new { sleep time; block.call } - end -end diff --git a/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb b/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb index b57bb68e0..7df0ad29e 100644 --- a/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/basics_spec.rb @@ -189,6 +189,7 @@ def count it "will use the promise returned by execute", js: true do isomorphic do class MyOperation < Hyperstack::Operation + include Hyperstack::AsyncSleep param :wait, type: Float, min: 0 param :result step do From fdad03d0ffdb62132d9d06bd62eec92edd091523 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 16 Feb 2021 21:12:19 -0500 Subject: [PATCH 085/307] i hope this helps --- .../lib/reactive_record/active_record/class_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb index 5f92c2b36..f9daab46a 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb @@ -454,7 +454,7 @@ def _react_param_conversion(param, opt = nil) [assoc.attribute, { id: [value]}] end else - [key, [value]] + [*key, [value]] end end.compact ReactiveRecord::Base.load_data do From f8f7e34467f5bd002e927f22602bf2fb8fb02f5e Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 18 Feb 2021 17:34:16 -0500 Subject: [PATCH 086/307] added docs --- docs/hyper-spec/01-installation.md | 57 +++ docs/hyper-spec/02-tutorial | 122 ++++++ .../03-hyperspec-methods-and-features.md | 383 ++++++++++++++++++ docs/hyper-spec/README.md | 41 ++ .../lib/hyper-spec/controller_helpers.rb | 2 +- 5 files changed, 604 insertions(+), 1 deletion(-) create mode 100644 docs/hyper-spec/01-installation.md create mode 100644 docs/hyper-spec/02-tutorial create mode 100644 docs/hyper-spec/03-hyperspec-methods-and-features.md create mode 100644 docs/hyper-spec/README.md diff --git a/docs/hyper-spec/01-installation.md b/docs/hyper-spec/01-installation.md new file mode 100644 index 000000000..11a2b79d9 --- /dev/null +++ b/docs/hyper-spec/01-installation.md @@ -0,0 +1,57 @@ +# HyperSpec Installation + +Add `gem 'hyper-spec'` to your Gemfile in the usual way. Typically in a Rails app you will add this in the test section of your Gemfile: + +```ruby +group :test do + gem 'hyper-spec', '~> 1.0.alpha1.0' +end +``` + +Make sure to `bundle install`. + +> Note: if you want to use the unreleased edge branch your hyper-spec gem specification will be: +> +> ```ruby +> gem 'hyper-spec', +> git: 'git://github.com/hyperstack-org/hyperstack.git', +> branch: 'edge', +> glob: 'ruby/*/*.gemspec' +> ``` + +HyperSpec is integrated with the `pry` gem for debugging, so it is recommended to add the `pry` gem as well. + +HyperSpec will also use the `timecop` gem if present to allow you to control and synchronize time on the server and the client. + +A typical spec_helper file when using HyperSpec will look like this: + +```ruby +# spec_helper.rb +require 'hyper-spec' +require 'pry' # optional + +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path('../test_app/config/environment', __FILE__) + +require 'rspec/rails' +require 'timecop' # optional + +# any other rspec configuration you need +# note HyperSpec will include chrome driver for providing the client +# run time environment +``` + +To load the webdriver and client environment your spec should have the +`:js` flag set: + +```ruby +# the js flag can be set on the entire group of specs, or a context +describe 'some hyper-specs', :js do + ... +end + +# or for an individual spec + it 'an individual hyper-spec', :js do + ... + end +``` diff --git a/docs/hyper-spec/02-tutorial b/docs/hyper-spec/02-tutorial new file mode 100644 index 000000000..de4f01011 --- /dev/null +++ b/docs/hyper-spec/02-tutorial @@ -0,0 +1,122 @@ +# Tutorial + +For this quick tutorial lets assume you have an existing Rails app that +already uses RSpec to which you have added a first Hyperstack component to +try things out. + +For your trial, you have created a very simple component that shows +the number of orders shipped by your companies website: + +```ruby +class OrdersShipped < HyperComponent + def format_number(number) + number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse + end + + render(DIV, class: 'orders-shipped') do + format_number Order.shipped.count + end +end +``` + +> Note that styling can be taken care of in the usual way by +> providing styles for the `orders-shipped` css class. All we care +> about here is the *function* of the component. + +Meanwhile `Order` is an ActiveRecord Model that would look something like this: + +```ruby +class Order < ApplicationRecord + ... + scope :shipped, -> () { where(status: :shipped) } + ... +end +``` + +> Note that when using ActiveRecord models in your specs you will +> need to add the appropriate database setup and cleaner methods like you would +> for any specs used with ActiveRecord. We assume here that as each +> spec starts there are no records in the database + +The `OrdersShipped` component can be mounted on any page of your site, +and assuming the proper policy permissions are provided it will +show the total orders shipped, and will dynamically increase in +realtime. + +A partial spec for this component might look like this: + +```ruby +require 'spec_helper' + +describe 'OrdersShipped', :js do + it 'dynamically displays the orders shipped' do + mount 'OrdersShipped' + expect(find('div.orders-shipped')).to have_content(0) + Order.create(status: :shipped) + expect(find('div.orders-shipped')).to have_content(1) + Order.last.destroy + expect(find('div.orders-shipped')).to have_content(0) + end + + it '#format method' do + on_client { @comp = OrdersShipped.new } + ['1,234,567', '123', '1,234'].each do |n| + expect { @comp.format_number(n.gsub(',','').to_i) } + .on_client_to eq(n) + end + end +end +``` + +If you are familiar with Capybara then the first spec should +look similar to an integration spec. The difference is instead +of visiting a page, we `mount` the `OrdersShipped` component on a blank page +that hyper-spec will set up for us. This let's us unit test +components outside of any application specific view logic. + +> Note that like Capybara we indicate that a client environment should +> be set up by adding the :js tag. + +Once mounted we can use Capybara finders and matchers, to check +if our content is as expected. Because we are running on the server +we can easily add and delete orders, and check the response on the UI. + +The second spec shows how we can do some white box unit testing of our +component. Instead of mounting the component we just create a new +instance which will be invisible since it was not mounted. For this we +use the `on_client` method. + +The `on_client` method takes a block, and will +compile that block using +Opal, and execute it on the client. In this case we simply create a +new `OrderShipped` instance, and assign it to an instance variable, which as you +will see will continue to be available to us later in the spec. + +> Note, if you are an RSpec purist, you would probably prefer to see +> something like `let` be used here instead of an instance variable. Shall we +> say its on the todo list. + +Now that we have our test component setup we can test its `format_number` +method. To do this we put the test expression in a block followed by +`on_client_to`. Again the block will be compiled using Opal, executed on +the client, and the result will be returned to the expectation. + +Notice that the server side variable `n` can be read (but not written) within +the client block. All local variables, memoized variables, and instance variables can +can be read in the client block as long as they represent objects that can be +sensibly marshalled and unmarshalled. + +This has covered the basics of Hyperspec - in summary: + ++ The `js` tag indicates the spec will be using a client environment. ++ `mount`: Mount a component on a blank page. This replaces the `visit` method +for unit testing components. ++ `on_client`: Execute Ruby code on the client (and return the result). ++ `on_client_to`: Execute the expectation block on the client, and then check +the expectation (on the server.) ++ Instance variables retain their values between client execution blocks. ++ All variables accessible to the spec are copied to the client if possible. + +There are many other features such as dealing with promises, passing data to +and from a mounted component, using the `Timecop` gem, and working with a `pry` +session. So read on. diff --git a/docs/hyper-spec/03-hyperspec-methods-and-features.md b/docs/hyper-spec/03-hyperspec-methods-and-features.md new file mode 100644 index 000000000..971bc62ec --- /dev/null +++ b/docs/hyper-spec/03-hyperspec-methods-and-features.md @@ -0,0 +1,383 @@ +# Summary of Methods and Features + +### Expectation Helpers + +These can be used any where within your specs: + ++ [`on_client`](#the-on_client-method) - executes code on the client ++ [`isomorphic`](#the-isomorphic-method) - executes code on the client *and* the server ++ [`mount`](#mounting-components) - mounts a hyperstack component in an empty window ++ [`before_mount`](#before_mount) - specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client` ++ [`client_options`](#client-initialization-options) - allows options to be specified globally ++ [`run_on_client`](#run_on_client) - same as `on_client` but no value is returned ++ [`reload_page`](#reload_page) - resets the page environment ++ [`add_class`](#add_class) - adds a CSS class ++ [`size_window`](#size_window) - specifies how big the client window should be ++ [`attributes_on_client`](#attributes_on_client) - returns any ActiveModel attributes loaded on the client + +These methods are used after mounting a component to retrieve +events sent outwards from the component: + ++ [`callback_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`last_callback_for`](#retrieving-event-data-from-the-mounted-component) ++ [`clear_callback_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`event_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`last_event_for`](#retrieving-event-data-from-the-mounted-component) ++ [`clear_event_history_for`](#retrieving-event-data-from-the-mounted-component) + +### Expectation Targets + +These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. + ++ [`on_client_to`](#client-expectation-targets), [`to_on_client_not`](#client-expectation-targets) - the expression will be evaluated on the client, and matched on the server. + +These methods have the following aliases to make your specs more readable: ++ [`to_on_client`](#client-expectation-targets) ++ [`on_client_to_not`](#client-expectation-targets) ++ [`on_client_not_to`](#client-expectation-targets) ++ [`to_not_on_client`](#client-expectation-targets) ++ [`not_to_on_client`](#client-expectation-targets) ++ [`to_then`](#client-expectation-targets) ++ [`then_to_not`](#client-expectation-targets) ++ [`then_not_to`](#client-expectation-targets) ++ [`to_not_then`](#client-expectation-targets) ++ [`not_to_then`](#client-expectation-targets) + + ++ [`with`](#client-expectation-targets) - can be chained with the above methods to pass data to initialize local variables on the client + +### Other Debugging Aids + +The following methods are used primarly at a debug break point, most require you use binding.pry as your debugger: + ++ [`to_js`](#to_js) - returns the ruby code compiled to JS. ++ [`ppr`](#ppr) - print the results of the ruby expression on the client console. ++ [`debugger`](#debugger) - Sets a debug breakpoint on code running on the client. ++ [`open_in_chrome`](#open_in_chrome) - Opens a chrome browser that will load the current state. ++ [`pause`](#pause) - Halts execution on the server without blocking I/O. + +### Available Webdrivers + +HyperSpec comes integrated with Chrome and Chrome headless webdrivers. The default configuration will run using Chrome headless. To see what is going on set the `DRIVER` environment variable to `chrome` +```bash +DRIVER=chrome bundle exec rspec +``` + +### Timecop Integration + +You can use the Timecop gem to control the flow of time within your specs. Hyperspec will coordinate things with the client so the time on the client is kept in sync with the time on the server. So for example if you use Timecop to advance time 1 day on the server, time on the browser will also advance by one day. + +See the [Client Initialization Options](#client-initialization-options) section for how to control the client time zone, and clock resolution. + +# Details + +### The `on_client` method + +The on_client method takes a block. The ruby code inside the block will be executed on the client, and the result will be returned. + +```ruby + it 'will print a message on the client' do + on_client do + puts 'hey I am running here on the client!' + end + end +``` + +If the block returns a promise Hyperspec will wait for the promise to be resolved before returning. For example: + +```ruby + it 'waits for a promise' do + start_time = Time.now + result = on_client do + promise = Promise.new + after(10.seconds) { promise.resolve('done!') } + promise + end + expect(result).to eq('done!') + expect(Time.now-start_time).to be >= 10.seconds + end +``` +> HyperSpec will do its best to reconstruct the result back on the server in some sensible way. Occasionally it just doesn't work, in which case you can end the block with a `nil` or some other simple expression, or use the `run_on_client` method, which does not return the result. + +### Accessing variables on the client + +It is often useful to pass variables from the spec to the client. Hyperspec will copy all your local variables, memoized variables, and instance variables known at the time the `on_client` block is compiled to the client.
+```ruby + let!(memoized) { 'a memoized variable' } + it 'will pass variables to the client' do + local = 'a local variable' + @instance = 'an instance variable' + result = on_client { [memoized, local, @instance] } + expect(result).to eq [memoized, local, @instance] + end +``` +> Note that memoized variables are not initialized until first +accessed, so you probably want to use the let! method unless you +are sure you are accessing the memoized value before sending it to the client. + +The value of instance variables initialized on the client are preserved +across blocks executed on the client. For example: +```ruby + it 'remembers instance variables' do + on_client { @total = 0 } + 10.times do |i| + # note how we are passing i in + on_client { @total += i } + end + result = on_client { @total } + expect(result).to eq(10 * 11 / 2) + end +``` + +### The `isomorphic` method + +The `isomorphic` works the same as `on_client` but in addition it also executes the same block on the server. It is especially useful when doing some testing of +ActiveRecord models, where you might want to modify the behavior of the model on server and the client. + +```ruby + it 'can run code the same everywhere!' do + isomorphic do + def factorial(x) + x.zero? ? 1 : x * factorial(x - 1) + end + end + + on_the_client = on_client { factorial(7) } + on_the_server = factorial(7) + expect(on_the_client).to eq(on_the_server) + end +``` + + +### Client Initialization Options + +The first time a spec runs code on the client, it has to initialize a browser context. You can use the `client_options` (aka `client_option`) method to specify the following options when the page is loaded. + ++ `time_zone` - browsers always run in the local time zone, if you want to force the browser to act as if its in a different zone, you can use the time_zone option, and provide any valid zone that the rails `in_time_zone` method will accept.
+Example: `client_option 'WorldClock', time_zone: 'Hawaii'` ++ `render_on`: `:client_only` (default), `:server_only`, or `:both` +Hyperstack components can be prerendered on the server. The `render_on` option controls this feature. For example `server_only` is useful to insure components are properly prerendered. *(See the mount method below for more details on rendering components)* ++ `clock_resolution`: Indicates the resolution that the simulated clock will run at on the client, when using the TimeCop gem. The default value is 20 (milliseconds). ++ `no_wait`: After the page is loaded the system will by default wait until all javascript requests to the server complete, before proceeding. Specifying `no_wait: true` will skip this. ++ `javascript`: The javascript asset to load when mounting the component. By default it will be `application` (.js is assumed). Note that the standard Hyperstack configuration will compile all the client side Ruby assets as well as javascript packages into the `application.js` file, so the default will work fine. ++ `style_sheet`: The style sheet asset to load when mounting the component. By default it will be `application` (.css is assumed). ++ `controller` - **expert zone** specify a controller that will be used to mount the +component. By default hyper-spec will build a controller and route to handle the request from the client to mount the component. + +Any other options not listed above will be passed along to the Rail's controller `render` method. So for example you could specify some other specific layout using `client_option layout: 'special_layout'` + +Note that this method can be used in the before(:each) block of a spec context, to provide options for all the specs in the block. + +### Mounting Components + +The `mount` method is used to render a component on a page: + +```ruby + it 'can display a component for me' do + mount 'SayHello', name: 'Lannar' do + class SayHello < HyperComponent + param :name + render(DIV) do + "Hello #{name}!" + end + end + end + + expect(page).to have_content('Hello Lannar') + end +``` + +The `mount` method has a few options. In it's simplest form you specify just the name of the component that is already defined in your hyperstack code and it will be mounted. + +You can add parameters that will be passed to the component as in the above example. As the above example also shows you can also define code within the block. The code does not have to be the component being mounted, but might be just some logic to help with the test. + +In addition `mount` can take any of the options provided to `client_options` (see above.) To provide these options, you must provide a (possibly) empty params hash. For example: +```ruby +mount 'MyComponent', {... params ... }, {... opts ... } +``` + +### Retrieving Event Data From the Mounted Component + +Components *receive* parameters, and may send callbacks and events back out. To test if a component has sent the appropriate data you can use the following methods: + ++ `callback_history_for` ++ `last_callback_for` ++ `clear_callback_history_for` ++ `event_history_for` ++ `last_event_for` ++ `clear_event_history_for` + +```ruby + it 'can check on a clients events and callbacks' do + mount 'BigTalker' do + class BigTalker < HyperComponent + fires :i_was_clicked + param :call_me_back, type: Proc + + before_mount { @click_counter = 0 } + + render(DIV) do + BUTTON { 'click me' }.on(:click) do + @click_counter += 1 + i_was_clicked! + call_me_back.call(@click_counter) + end + end + end + end + 3.times do + find('button').click + end + # the history is an array, one element for each item in the history + expect(event_history_for(:i_was_clicked).length).to eq(3) + # each item in the array is itself an array of the arguments + expect(last_call_back_for(:call_me_back)).to eq([3]) + # clearing the history resets the array to empty + clear_event_history_for(:i_was_clicked) + expect(event_history_for(:i_was_clicked).length).to eq(0) + end +``` + +> Note that you must declare the params as type Proc, or use +the fires method to declare an event, for the history mechanism to work. + +### Other Helpers + +#### `before_mount` + +Specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client`. This is primarly useful to add to a rspec before(:each) block containing common client code needed by all the specs in the context. + +> Unlike `mount`, `isomorphic` and `on_client`, `before_mount` does not load the client page, but will wait for the first of the other methods to be called. + +#### `run_on_client` + +same as `on_client` but no value is returned. Useful when the return value may be to complex to marshall and unmarshall using JSON. + +#### `reload_page` + +Shorthand for `mount` with no parameters. Useful if you need to reset the client within a spec. + +#### `add_class` + +Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the react style format. + +Example: `add_class :some_class, borderStyle: :solid` adds a class with style `border-style: 'solid'` + +#### `size_window` + +Indicates the size of the browser window. The values can be given either symbolically or as two numbers (width and height). Predefined sizes are: + ++ `:small`: 480 x 320 ++ `:mobile` 640 x 480 ++ `:tablet` 960 x 64, ++ `:large` 1920 x 6000 ++ `:default` 1024 x 768 + +All of the above can be modified by providing the `:portrait` option as the first or second parameter. + +So for example the following are all equivalent: + ++ `size_window(:small, :portrait)` ++ `size_window(:portrait, :small)` ++ `size_window(320, 480)` + +#### `attributes_on_client` + +returns any `ActiveModel` attributes loaded on the client. HyperModel will normally begin a load cycle as soon as you access the attribute of a client. However it is sometimes useful to see what attributes have already been loaded. + +### Client Expectation Targets + +These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. + +For example: + +```ruby +it 'has built-in expectation targets' do + expect { RUBY_ENGINE }.on_client_to eq('opal') +end +``` + +The above expectation is short for saying: + +```ruby + result = on_client { RUBY_ENGINE } + expect(result).to eq('opal') +``` + +These methods have the following aliases to make your specs more readable: ++ `to_on_client` ++ `on_client_to_not` ++ `on_client_not_to` ++ `to_not_on_client` ++ `not_to_on_client` ++ `to_then` ++ `then_to_not` ++ `then_not_to` ++ `to_not_then` ++ `not_to_then` + +The `then` variants are useful to note that the spec involves a promise, but it does no explicit checking that the result comes from a promise. + +In addition the `with` method can be chained with the above methods to pass data to initialize local variables on the client: + +```ruby + it 'can pass values to the client using the with method' do + expect { foo * foo }.with(foo: 12).to_on_client eq(144) + end +``` + +Note that there are other ways to pass values into the client context as noted above but the `with` might be preferable to some to keep things +nicely localized. + +### Useful Debug Methods + +These methods are primarily designed to help debug code and specs. + +#### `to_js` + +Takes a block like `on_client` but rather than running the code on the client, simply returns the resulting code. This is useful for debugging obscure problems when the Opal compiler or some feature of +Hyperspec is suspected as the issue. + +#### `ppr` + +Takes a block like `on_client` and prints the result on the client console using JS console.log. Equivalent to doing + +```ruby + on_client do + begin + ... + end.tap { |r| `console.log(r)` } + end +``` + +This is useful when the result cannot be usefully returned to the server, +or when the result of interest is better looked at as the raw +javascript object. + +#### `c?` + +Shorthand for `on_console`, useful for entering expressions in pry console, to investigate the state of the client. + +#### `debugger` + +This psuedo method can be inserted into any code executed on the client. It will cause the code to stop, and enter a *javascript* read-eval loop, within the debug console. + +Unfortunately ATM we do not have the technology to enter a *Ruby* read-eval loop at an arbitrary point on the client. + +> Note: due to a bug in the Opal compiler your code should not have `debugger` as the last expression in a method or a block. In this situation add any expression (such as nil) after the debugger statement. +```ruby +def foo + ... some code ... + debugger # this will fail with a compiler syntax error +end +``` + +#### `open_in_chrome` +By default specs are run with headless chrome, so there is no visible browser window. The `open_in_chrome` method will open a browser window, and load it with the current state. + +You can also run specs in a visible chrome window by setting the `DRIVER` environment variable to `CHROME` + +#### `pause` +The method is typically not needed assuming you are using a multithreaded server like Puma. If for whatever reason the pry debug session is not multithreaded, *and* you want to try some kind of experiment on the javascript console, *and* those experiments make requests to the server, you may not get a response, because all threads are in use. + +You can resolve this by using the `pause` method in the debug session which will put the debug session into a non-blocking loop. You then release the pause by executing `go()` in the *javascript* debug console. diff --git a/docs/hyper-spec/README.md b/docs/hyper-spec/README.md new file mode 100644 index 000000000..44dd165e3 --- /dev/null +++ b/docs/hyper-spec/README.md @@ -0,0 +1,41 @@ +# HyperSpec + +## Adding client side testing to RSpec + +The `hyper-spec` gem supports the Hyperstack goals of programmer productivity and seamless web development by allowing testing to be done with minimal concern for the client-server interface. + +The `hyper-spec` gem adds functionality to the `rspec`, `capybara`, `timecop` and `pry` gems allowing you to do the following: + ++ write component and integration tests using the rspec syntax and helpers ++ write specs that run on both the client and server ++ evaluate client side ruby expressions from within specs and while using `pry` ++ share data between the client and server within your specs ++ control and synchronize time on the client and the server + +HyperSpec can be used standalone, but if used as part of a Hyperstack application it allows straight forward testing of Hyperstack Components and your ActiveRecord Models. + +So for example here is part of a simple unit test of a TodoIndex component: + +```ruby +it "will update the TodoIndex", js: true do + # mounts the TodoIndex component (client side) + mount 'TodoIndex' + # Todo is an ActiveRecord Model + # create a new Todo on the server (we could use FactoryBot of course) + todo_1 = Todo.create(title: 'this todo created on the server') + # verify that UI got updated + expect(find('.ToDoItem-Text').text).to eq todo_1.title + # verify that the count of Todos on the client side DB matches the server + expect { Todo.count }.on_client_to eq Todo.count + # now create another Todo on the client + new_todo_title = 'this todo created on the client' + # note that local variables are copied from the server to the client + on_client { Todo.create(title: new_todo_title) } + # the Todo should now be reflected on the server + expect(Todo.last.title).to eq new_todo_title +end +``` + +When using HyperSpec all the specs execute on the server side, but they may also interrogate the state of the UI as well as the state +of any of the client side objects. The specs can execute any valid Ruby code client side to create new test objects as well as do +white box testing. This keeps the logic of your specs in one place. diff --git a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb index b2a6a0b4d..5aa2129c5 100644 --- a/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/controller_helpers.rb @@ -31,7 +31,7 @@ def style_sheet!(_file_) # deliver the page. The @page variable will contain the html ready to go, # any additional options that are passed through from the spec will be - # in the @render_params variable. For example layout: 'my special layout' + # in the @render_params variable. For example layout: 'my_special_layout' def deliver! raise 'must implement' From 54e5a1cf5abf7edd6be3a9d242cd14e75704d3c4 Mon Sep 17 00:00:00 2001 From: Mitch VanDuyn Date: Thu, 18 Feb 2021 17:41:16 -0500 Subject: [PATCH 087/307] Update 03-hyperspec-methods-and-features.md fixed spacing problem --- docs/hyper-spec/03-hyperspec-methods-and-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hyper-spec/03-hyperspec-methods-and-features.md b/docs/hyper-spec/03-hyperspec-methods-and-features.md index 971bc62ec..00216fc16 100644 --- a/docs/hyper-spec/03-hyperspec-methods-and-features.md +++ b/docs/hyper-spec/03-hyperspec-methods-and-features.md @@ -43,7 +43,7 @@ These methods have the following aliases to make your specs more readable: + [`to_not_then`](#client-expectation-targets) + [`not_to_then`](#client-expectation-targets) - +in addition + [`with`](#client-expectation-targets) - can be chained with the above methods to pass data to initialize local variables on the client ### Other Debugging Aids From 78b271837cc08f4bfec0303cda7fbf85166d9a65 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 19 Feb 2021 19:31:59 -0500 Subject: [PATCH 088/307] updated docs fixed prerendering issue --- docs/hyper-spec/02-tutorial | 4 +- .../03-hyperspec-methods-and-features.md | 76 +++++-- .../server_rendering/hyper_asset_container.rb | 6 +- ruby/hyper-spec/lib/hyper-spec.rb | 1 + ruby/hyper-spec/lib/hyper-spec/helpers.rb | 3 + .../hyper-spec/internal/client_execution.rb | 41 ---- .../hyper-spec/internal/component_mount.rb | 18 +- .../lib/hyper-spec/internal/copy_locals.rb | 103 ++++++++++ ruby/hyper-spec/spec/hyper_spec.rb | 190 +++++++++++++++--- 9 files changed, 352 insertions(+), 90 deletions(-) create mode 100644 ruby/hyper-spec/lib/hyper-spec/internal/copy_locals.rb diff --git a/docs/hyper-spec/02-tutorial b/docs/hyper-spec/02-tutorial index de4f01011..8d99964a4 100644 --- a/docs/hyper-spec/02-tutorial +++ b/docs/hyper-spec/02-tutorial @@ -96,7 +96,7 @@ will see will continue to be available to us later in the spec. > something like `let` be used here instead of an instance variable. Shall we > say its on the todo list. -Now that we have our test component setup we can test its `format_number` +Now that we have our test component setup we can test it's `format_number` method. To do this we put the test expression in a block followed by `on_client_to`. Again the block will be compiled using Opal, executed on the client, and the result will be returned to the expectation. @@ -113,7 +113,7 @@ This has covered the basics of Hyperspec - in summary: for unit testing components. + `on_client`: Execute Ruby code on the client (and return the result). + `on_client_to`: Execute the expectation block on the client, and then check -the expectation (on the server.) +the expectation (on the server.) + Instance variables retain their values between client execution blocks. + All variables accessible to the spec are copied to the client if possible. diff --git a/docs/hyper-spec/03-hyperspec-methods-and-features.md b/docs/hyper-spec/03-hyperspec-methods-and-features.md index 971bc62ec..8883d99bf 100644 --- a/docs/hyper-spec/03-hyperspec-methods-and-features.md +++ b/docs/hyper-spec/03-hyperspec-methods-and-features.md @@ -1,4 +1,4 @@ -# Summary of Methods and Features +# HyperSpec Methods and Features ### Expectation Helpers @@ -69,6 +69,10 @@ You can use the Timecop gem to control the flow of time within your specs. Hype See the [Client Initialization Options](#client-initialization-options) section for how to control the client time zone, and clock resolution. +### The `no_reset` flag + +By default the client environment will be reinitialized at the beginning of every spec. If this is not needed you can speed things up by adding the `no_reset` flag to a block of specs. + # Details ### The `on_client` method @@ -129,9 +133,42 @@ across blocks executed on the client. For example: end ``` +> Be especially careful of this this when using the [`no_reset`](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. + +#### White and Black Listing Variables + +By default all local variables, memoized variables, and instance variables in scope in the spec will be copied to the client. This can be controlled through the `include_vars` and `exclude_vars` [client options](#client-initialization-options). + +`include_vars` can be set to ++ an array of symbols: only those vars will be copied, ++ a single symbol: only that var will be copied, ++ any other truthy value: all vars will be copied (the default) ++ or nil, false, or an empty array: no vars will be copied. + +`exclude_vars` can be set to ++ an array of symbols - those vars will **not** be copied, ++ a single symbol - only that var will be excluded, ++ any other truthy value - no vars will be copied, ++ or nil, false, or an empty array - all vars will be copied (the default). + +Examples: + +```Ruby + # don't copy vars at all. + client_option exclude_vars: true + # only copy var1 and the instance var @var2 + client_option include_vars: [:var1, :@var2] + # only exclude foo_var + client_option exclude_vars: :foo_var +``` + +Note that the exclude_vars list will take precedence over the include_vars list. + +The exclude/include lists can be overridden on an individual spec using the `with` method - See [Client Expectation Targets](#client-expectation-targets). + ### The `isomorphic` method -The `isomorphic` works the same as `on_client` but in addition it also executes the same block on the server. It is especially useful when doing some testing of +The `isomorphic` method works the same as `on_client` but in addition it also executes the same block on the server. It is especially useful when doing some testing of ActiveRecord models, where you might want to modify the behavior of the model on server and the client. ```ruby @@ -148,25 +185,26 @@ ActiveRecord models, where you might want to modify the behavior of the model on end ``` - ### Client Initialization Options The first time a spec runs code on the client, it has to initialize a browser context. You can use the `client_options` (aka `client_option`) method to specify the following options when the page is loaded. + `time_zone` - browsers always run in the local time zone, if you want to force the browser to act as if its in a different zone, you can use the time_zone option, and provide any valid zone that the rails `in_time_zone` method will accept.
-Example: `client_option 'WorldClock', time_zone: 'Hawaii'` -+ `render_on`: `:client_only` (default), `:server_only`, or `:both` -Hyperstack components can be prerendered on the server. The `render_on` option controls this feature. For example `server_only` is useful to insure components are properly prerendered. *(See the mount method below for more details on rendering components)* +Example: `client_option time_zone: 'Hawaii'` + `clock_resolution`: Indicates the resolution that the simulated clock will run at on the client, when using the TimeCop gem. The default value is 20 (milliseconds). ++ `include_vars`: white list of all vars to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. ++ `exclude_vars`: black list of all vars not to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. ++ `render_on`: `:client_only` (default), `:server_only`, or `:both` +Hyperstack components can be prerendered on the server. The `render_on` option controls this feature. For example `server_only` is useful to insure components are properly prerendered. *See the `mount` method [below](#mounting-components) for more details on rendering components* + `no_wait`: After the page is loaded the system will by default wait until all javascript requests to the server complete, before proceeding. Specifying `no_wait: true` will skip this. + `javascript`: The javascript asset to load when mounting the component. By default it will be `application` (.js is assumed). Note that the standard Hyperstack configuration will compile all the client side Ruby assets as well as javascript packages into the `application.js` file, so the default will work fine. + `style_sheet`: The style sheet asset to load when mounting the component. By default it will be `application` (.css is assumed). -+ `controller` - **expert zone** specify a controller that will be used to mount the ++ `controller` - **(expert zone!)** specify a controller that will be used to mount the component. By default hyper-spec will build a controller and route to handle the request from the client to mount the component. Any other options not listed above will be passed along to the Rail's controller `render` method. So for example you could specify some other specific layout using `client_option layout: 'special_layout'` -Note that this method can be used in the before(:each) block of a spec context, to provide options for all the specs in the block. +Note that this method can be used in the `before(:each)` block of a spec context to provide options for all the specs in the block. ### Mounting Components @@ -189,7 +227,7 @@ The `mount` method is used to render a component on a page: The `mount` method has a few options. In it's simplest form you specify just the name of the component that is already defined in your hyperstack code and it will be mounted. -You can add parameters that will be passed to the component as in the above example. As the above example also shows you can also define code within the block. The code does not have to be the component being mounted, but might be just some logic to help with the test. +You can add parameters that will be passed to the component as in the above example. As the above example also shows you can also define code within the block. This is just shorthand for defining the code before hand using `on_client`. The code does not have to be the component being mounted, but might be just some logic to help with the test. In addition `mount` can take any of the options provided to `client_options` (see above.) To provide these options, you must provide a (possibly) empty params hash. For example: ```ruby @@ -238,20 +276,20 @@ Components *receive* parameters, and may send callbacks and events back out. To end ``` -> Note that you must declare the params as type Proc, or use -the fires method to declare an event, for the history mechanism to work. +> Note that you must declare the params as type `Proc`, or use +the `fires` method to declare an event for the history mechanism to work. ### Other Helpers #### `before_mount` -Specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client`. This is primarly useful to add to a rspec before(:each) block containing common client code needed by all the specs in the context. +Specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client`. This is primarly useful to add to an rspec `before(:each)` block containing common client code needed by all the specs in the context. > Unlike `mount`, `isomorphic` and `on_client`, `before_mount` does not load the client page, but will wait for the first of the other methods to be called. #### `run_on_client` -same as `on_client` but no value is returned. Useful when the return value may be to complex to marshall and unmarshall using JSON. +same as `on_client` but no value is returned. Useful when the return value may be too complex to marshall and unmarshall using JSON. #### `reload_page` @@ -259,7 +297,7 @@ Shorthand for `mount` with no parameters. Useful if you need to reset the clie #### `add_class` -Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the react style format. +Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the React [style format.](https://reactjs.org/docs/dom-elements.html#style) Example: `add_class :some_class, borderStyle: :solid` adds a class with style `border-style: 'solid'` @@ -283,7 +321,7 @@ So for example the following are all equivalent: #### `attributes_on_client` -returns any `ActiveModel` attributes loaded on the client. HyperModel will normally begin a load cycle as soon as you access the attribute of a client. However it is sometimes useful to see what attributes have already been loaded. +returns any `ActiveModel` attributes loaded on the client. HyperModel will normally begin a load cycle as soon as you access the attribute on the client. However it is sometimes useful to see what attributes have already been loaded. ### Client Expectation Targets @@ -326,8 +364,7 @@ In addition the `with` method can be chained with the above methods to pass data end ``` -Note that there are other ways to pass values into the client context as noted above but the `with` might be preferable to some to keep things -nicely localized. +By default HyperSpec will copy all local variables, memoized variables, and instance variables defined in a spec to the client. The specific variables can also be white listed and black listed. The `with` method overrides any white or black listed values. So for example if you prefer to use the more explicit `with` method to pass values to the client, you can add `client_option exclude_vars: true` in a `before(:all)` block in your spec helper. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. ### Useful Debug Methods @@ -358,6 +395,11 @@ javascript object. Shorthand for `on_console`, useful for entering expressions in pry console, to investigate the state of the client. +```ruby +pry:> c? { puts 'hello on the console' } # prints hello on the client +-> nil +``` + #### `debugger` This psuedo method can be inserted into any code executed on the client. It will cause the code to stop, and enter a *javascript* read-eval loop, within the debug console. diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb index 2b9dfbb5a..f253a671e 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rails/server_rendering/hyper_asset_container.rb @@ -9,7 +9,9 @@ module Rails module ServerRendering class HyperTestAssetContainer def find_asset(logical_path) - HyperSpec::ComponentTestHelpers.cache_read(logical_path) + # we skip the container if it raises an error so we + # don't care if we are running under hyperspec or not + HyperSpec::Internal::Controller.cache_read(logical_path) end end @@ -24,7 +26,7 @@ def initialize if React::ServerRendering::WebpackerManifestContainer.compatible? @ass_containers << React::ServerRendering::WebpackerManifestContainer.new end - @ass_containers << HyperTestAssetContainer.new if ::Rails.env.test? + @ass_containers << HyperTestAssetContainer.new end def find_asset(logical_path) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 720054a1c..07f8a1927 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -9,6 +9,7 @@ require 'hyper-spec/internal/client_execution' require 'hyper-spec/internal/component_mount' require 'hyper-spec/internal/controller' +require 'hyper-spec/internal/copy_locals' require 'hyper-spec/internal/patches' require 'hyper-spec/internal/rails_controller_helpers' require 'hyper-spec/internal/time_cop.rb' diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index 19bb05559..b769d6146 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -3,6 +3,7 @@ module Helpers include Internal::ClientExecution include Internal::Controller include Internal::ComponentMount + include Internal::CopyLocals include Internal::WindowSizing ## @@ -90,6 +91,8 @@ def isomorphic(&block) def client_option(opts = {}) @_hyperspec_private_client_options ||= {} @_hyperspec_private_client_options.merge! opts + build_var_inclusion_lists + @_hyperspec_private_client_options end alias client_options client_option diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb index 66c0004c4..51f65c52f 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb @@ -8,36 +8,6 @@ def internal_evaluate_ruby(*args, &block) private - PRIVATE_VARIABLES = %i[ - @__inspect_output @__memoized @example @_hyperspec_private_client_code - @_hyperspec_private_html_block @fixture_cache - @fixture_connections @connection_subscriber @loaded_fixtures - @_hyperspec_private_client_options - b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_ - ] - - def add_locals(in_str, block) - b = block.binding - - memoized = b.eval('__memoized').instance_variable_get(:@memoized) - in_str = memoized.inject(in_str) do |str, pair| - "#{str}\n#{set_local_var(pair.first, pair.last)}" - end if memoized - - in_str = b.local_variables.inject(in_str) do |str, var| - next str if PRIVATE_VARIABLES.include? var - - "#{str}\n#{set_local_var(var, b.local_variable_get(var))}" - end - - in_str = b.eval('instance_variables').inject(in_str) do |str, var| - next str if PRIVATE_VARIABLES.include? var - - "#{str}\n#{set_local_var(var, b.eval("instance_variable_get('#{var}')"))}" - end - in_str - end - def add_opal_block(str, block) return str unless block @@ -113,17 +83,6 @@ def the_node_you_are_looking_for?(node) node.children.first.type == :send end - def set_local_var(name, object) - serialized = object.opal_serialize - if serialized - "#{name} = #{serialized}" - else - "self.class.define_method(:#{name}) "\ - "{ raise 'Attempt to access the variable #{name} "\ - 'that was defined in the spec, but its value could not be serialized '\ - "so it is undefined on the client.' }" - end - end def opal_compile(str, *) Opal.hyperspec_compile(str) diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb index f57524932..04d90a11e 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb @@ -91,6 +91,22 @@ def send_params_to_controller_via_cache(test_url, component_name, params, opts) @_hyperspec_private_html_block = nil end + # test_code_key = "hyper_spec_prerender_test_code.js" + # if defined? ::Hyperstack::Component + # @@original_server_render_files ||= ::Rails.configuration.react.server_renderer_options[:files] + # if opts[:render_on] == :both || opts[:render_on] == :server_only + # unless opts[:code].blank? + # ComponentTestHelpers.cache_write(test_code_key, opts[:code]) + # ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + [test_code_key] + # ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes + # else + # ComponentTestHelpers.cache_delete(test_code_key) + # ::Rails.configuration.react.server_renderer_options[:files] = @@original_server_render_files + # ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher doesnt look for cache changes + # end + # end + # end + def setup_prerendering(opts) return unless defined?(::Hyperstack::Component) && prerendering?(opts) @@ -100,7 +116,7 @@ def setup_prerendering(opts) Controller.cache_delete(TEST_CODE_KEY) else Controller.cache_write(TEST_CODE_KEY, opts[:code]) - @@original_server_render_files += [TEST_CODE_KEY] + ::Rails.configuration.react.server_renderer_options[:files] += [TEST_CODE_KEY] end ::React::ServerRendering.reset_pool # make sure contexts are reloaded so they dont use code from cache, as the rails filewatcher diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/copy_locals.rb b/ruby/hyper-spec/lib/hyper-spec/internal/copy_locals.rb new file mode 100644 index 000000000..6ba1e8a33 --- /dev/null +++ b/ruby/hyper-spec/lib/hyper-spec/internal/copy_locals.rb @@ -0,0 +1,103 @@ +module HyperSpec + module Internal + module CopyLocals + private + + def build_var_inclusion_lists + build_included_list + build_excluded_list + end + + def build_included_list + @_hyperspec_private_included_vars = nil + return unless @_hyperspec_private_client_options.key? :include_vars + + included = @_hyperspec_private_client_options[:include_vars] + if included.is_a? Symbol + @_hyperspec_private_included_vars = [included] + elsif included.is_a?(Array) + @_hyperspec_private_included_vars = included + elsif !included + @_hyperspec_private_included_vars = [] + end + end + + PRIVATE_VARIABLES = %i[ + @__inspect_output @__memoized @example @_hyperspec_private_client_code + @_hyperspec_private_html_block @fixture_cache + @fixture_connections @connection_subscriber @loaded_fixtures + @_hyperspec_private_client_options + @_hyperspec_private_included_vars + @_hyperspec_private_excluded_vars + b __ _ _ex_ pry_instance _out_ _in_ _dir_ _file_ + ] + + def build_excluded_list + return unless @_hyperspec_private_client_options + + excluded = @_hyperspec_private_client_options[:exclude_vars] + if excluded.is_a? Symbol + @_hyperspec_private_excluded_vars = [excluded] + elsif excluded.is_a?(Array) + @_hyperspec_private_excluded_vars = excluded + elsif excluded + @_hyperspec_private_included_vars = [] + end + end + + def var_excluded?(var, binding) + return true if PRIVATE_VARIABLES.include? var + + excluded = binding.eval('instance_variable_get(:@_hyperspec_private_excluded_vars)') + return true if excluded&.include?(var) + + included = binding.eval('instance_variable_get(:@_hyperspec_private_included_vars)') + included && !included.include?(var) + end + + def add_locals(in_str, block) + b = block.binding + add_instance_vars(b, add_local_vars(b, add_memoized_vars(b, in_str))) + end + + def add_memoized_vars(binding, in_str) + memoized = binding.eval('__memoized').instance_variable_get(:@memoized) + return in_str unless memoized + + memoized.inject(in_str) do |str, pair| + next str if var_excluded?(pair.first, binding) + + "#{str}\n#{set_local_var(pair.first, pair.last)}" + end + end + + def add_local_vars(binding, in_str) + binding.local_variables.inject(in_str) do |str, var| + next str if var_excluded?(var, binding) + + "#{str}\n#{set_local_var(var, binding.local_variable_get(var))}" + end + end + + def add_instance_vars(binding, in_str) + binding.eval('instance_variables').inject(in_str) do |str, var| + next str if var_excluded?(var, binding) + + "#{str}\n#{set_local_var(var, binding.eval("instance_variable_get('#{var}')"))}" + end + end + + def set_local_var(name, object) + serialized = object.opal_serialize + if serialized + "#{name} = #{serialized}" + else + "self.class.define_method(:#{name}) "\ + "{ raise 'Attempt to access the variable #{name} "\ + 'that was defined in the spec, but its value could not be serialized '\ + "so it is undefined on the client.' }" + end + end + end + end +end diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index b2f7744cb..f9456502a 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -32,6 +32,21 @@ class ShowOff expect(evaluate_script('typeof React')).to eq('undefined') end + it "can render server side only with code defined in the mount", :prerendering_on do + client_option render_on: :server_only + mount 'SayHello2', name: 'George' do + class SayHello2 + include Hyperstack::Component + param :name + render(DIV) do + "Hello there #{@Name}" + end + end + end + expect(page).to have_content('Hello there George') + expect(evaluate_script('typeof React')).to eq('undefined') + end + it "can use the application's layout" do client_option layout: 'application' mount 'SayHello', name: 'Sam' @@ -230,6 +245,28 @@ class StyledDiv end end + context 'the no-reset flag', :no_reset do + it 'will mount the component first' do + mount 'TestComponent' do + class TestComponent + include Hyperstack::Component + include Hyperstack::State::Observable + class << self + state_accessor :title + end + render(DIV) do + TestComponent.title + end + end + end + on_client { TestComponent.title = 'The Title' } + expect(page).to have_content('The Title') + end + it 'but will not mount it again' do + expect(page).to have_content('The Title') + end + end + context "new style rspec expressions", no_reset: true do before(:each) do @@ -259,38 +296,137 @@ class StyledDiv end.to_then eq('done') end - it 'will copy local vars to the client' do - str = 'hello' - expect { str.reverse }.on_client_to eq str.reverse - end + context 'copying local vars:' do + let!(:memoized_var) { true } + let!(:another_memoized_var) { true } + before(:each) do + on_client do + send(:remove_instance_variable, :@instance_var) rescue nil + send(:remove_instance_variable, :@another_instance_var) rescue nil + end + end - it 'will copy instance vars to the client' do - expect { @str.reverse }.on_client_to eq @str.reverse - end + it 'will copy local vars to the client' do + str = 'hello' + expect { str.reverse }.on_client_to eq str.reverse + end - it 'will copy memoized values to the client' do - expect { another_string.gsub(/\W/, '') }.on_client_to eq another_string.gsub(/\W/, '') - end + it 'will copy instance vars to the client' do + expect { @str.reverse }.on_client_to eq @str.reverse + end - it 'will deal with unserailized local vars, instance vars and memoized values correctly' do - foo_bar = page - expect do - evaluate_ruby { foo_bar } - end.to raise_error(Exception, /foo_bar/) - end + it 'will copy memoized values to the client' do + expect { another_string.gsub(/\W/, '') }.on_client_to eq another_string.gsub(/\W/, '') + end - it 'will ignore unserailized local vars, instance vars and memoized values if not accessed' do - foo_bar = page - good_value = 12 - expect { good_value * 2 }.on_client_to eq good_value * 2 - end + it 'will deal with unserailized local vars, instance vars and memoized values correctly' do + foo_bar = page + expect do + evaluate_ruby { foo_bar } + end.to raise_error(Exception, /foo_bar/) + end - it 'will allow unserailized local vars, instance vars and memoized values can be redefined on the client' do - foo_bar = page - expect do - foo_bar = 12 - foo_bar * 2 - end.on_client_to eq 24 + it 'will ignore unserailized local vars, instance vars and memoized values if not accessed' do + foo_bar = page + good_value = 12 + expect { good_value * 2 }.on_client_to eq good_value * 2 + end + + it 'will allow unserailized local vars, instance vars and memoized values can be redefined on the client' do + foo_bar = page + expect do + foo_bar = 12 + foo_bar * 2 + end.on_client_to eq 24 + end + + context 'the include_vars option' do + [false, nil, []].each do |include_vars| + it "will not copy any vars if the include_vars option is #{include_vars}" do + client_option include_vars: include_vars + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + end + end + it "will copy all the vars if the include_vars option is a non-array truthy value" do + client_option include_vars: 123 + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be true + expect { local_var }.on_client_to be true + expect { memoized_var }.on_client_to be true + end + %i[@instance_var memoized_var local_var].each do |var| + it "will copy only a single var if the include_vars option is a name like #{var}" do + client_option include_vars: var + @instance_var = true + local_var = true + expect { @instance_var.nil? }.on_client_to eq(var != :@instance_var) + expect { !!defined?(local_var) }.on_client_to eq(var == :local_var) + expect { !!defined?(memoized_var) }.on_client_to eq(var == :memoized_var) + end + end + it 'will only copy vars listed in the include_vars option' do + client_option include_vars: [:another_memoized_var, :@another_instance_var, :another_local_var] + @instance_var = true + local_var = true + @another_instance_var = true + another_local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + expect { @another_instance_var }.on_client_to be true + expect { another_local_var }.on_client_to be true + expect { another_memoized_var }.on_client_to be true + end + end + + context 'the exclude_vars option' do + [false, nil, []].each do |exclude_vars| + it "will copy all vars if the exclude_vars option is #{exclude_vars}" do + client_option exclude_vars: exclude_vars + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be true + expect { local_var }.on_client_to be true + expect { memoized_var }.on_client_to be true + end + end + it "will not copy any vars if the exclude_vars option is a non-array truthy value" do + client_option exclude_vars: 123 + @instance_var = true + local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + end + %i[@instance_var memoized_var local_var].each do |var| + it "will exclude a single var if the exclude_vars option is a name like #{var}" do + client_option exclude_vars: var + @instance_var = true + local_var = true + expect { @instance_var.nil? }.on_client_to eq(var == :@instance_var) + expect { !!defined?(local_var) }.on_client_to eq(var != :local_var) + expect { !!defined?(memoized_var) }.on_client_to eq(var != :memoized_var) + end + end + it 'will not copy vars listed in the exclude_vars option' do + client_option exclude_vars: [:memoized_var, :@instance_var, :local_var] + @instance_var = true + local_var = true + @another_instance_var = true + another_local_var = true + expect { @instance_var }.on_client_to be_nil + expect { defined?(local_var) }.on_client_to be_falsy + expect { defined?(memoized_var) }.on_client_to be_falsy + expect { @another_instance_var }.on_client_to be true + expect { another_local_var }.on_client_to be true + expect { another_memoized_var }.on_client_to be true + end + end end it 'aliases evaluate_ruby as on_client and c?' do From 17e99db5b8004775734401032f528087e0d64ee9 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 19 Feb 2021 20:40:36 -0500 Subject: [PATCH 089/307] fixed hyper-operation monkey patch for new hyper-spec compatibility --- ruby/hyper-operation/spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-operation/spec/spec_helper.rb b/ruby/hyper-operation/spec/spec_helper.rb index b7432f835..c773c3b06 100644 --- a/ruby/hyper-operation/spec/spec_helper.rb +++ b/ruby/hyper-operation/spec/spec_helper.rb @@ -137,7 +137,7 @@ def self.on_server? end module HyperSpec - module ComponentTestHelpers + module Helpers alias old_expect_promise expect_promise def expect_promise(str_or_promise = nil, &block) if str_or_promise.is_a? Promise From cbfd8de4abc7241bb8e0d861234e04ff860fcaec Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 19 Feb 2021 21:38:26 -0500 Subject: [PATCH 090/307] use internal_evaluate_ruby instead of on_client for backwards compatibility --- docs/hyper-spec/{02-tutorial => 02-tutorial.md} | 0 ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/hyper-spec/{02-tutorial => 02-tutorial.md} (100%) diff --git a/docs/hyper-spec/02-tutorial b/docs/hyper-spec/02-tutorial.md similarity index 100% rename from docs/hyper-spec/02-tutorial rename to docs/hyper-spec/02-tutorial.md diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb index faf8d8de8..35437a9f2 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/time_cop.rb @@ -148,7 +148,7 @@ def pending_evaluations def evaluate_ruby(&block) if @capybara_page - @capybara_page.on_client(yield) + @capybara_page.internal_evaluate_ruby(yield) else pending_evaluations << block end @@ -156,7 +156,7 @@ def evaluate_ruby(&block) def run_pending_evaluations return if pending_evaluations.empty? - @capybara_page.on_client(pending_evaluations.collect(&:call).join("\n")) + @capybara_page.internal_evaluate_ruby(pending_evaluations.collect(&:call).join("\n")) @pending_evaluations ||= [] end end From 8c3be119bd5183c5f6bfa9f9dcece87e70267678 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 20 Feb 2021 10:17:37 -0500 Subject: [PATCH 091/307] doc and code cleanups added insert_html spec --- docs/hyper-spec/02-tutorial.md | 6 +- .../03-hyperspec-methods-and-features.md | 57 ++++++++++++------- .../hyper-spec/internal/component_mount.rb | 2 + ruby/hyper-spec/spec/hyper_spec.rb | 10 +++- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/docs/hyper-spec/02-tutorial.md b/docs/hyper-spec/02-tutorial.md index 8d99964a4..d988dc86c 100644 --- a/docs/hyper-spec/02-tutorial.md +++ b/docs/hyper-spec/02-tutorial.md @@ -71,13 +71,13 @@ end If you are familiar with Capybara then the first spec should look similar to an integration spec. The difference is instead of visiting a page, we `mount` the `OrdersShipped` component on a blank page -that hyper-spec will set up for us. This let's us unit test +that hyper-spec will set up for us. This lets us unit test components outside of any application specific view logic. > Note that like Capybara we indicate that a client environment should > be set up by adding the :js tag. -Once mounted we can use Capybara finders and matchers, to check +Once mounted we can use Capybara finders and matchers to check if our content is as expected. Because we are running on the server we can easily add and delete orders, and check the response on the UI. @@ -96,7 +96,7 @@ will see will continue to be available to us later in the spec. > something like `let` be used here instead of an instance variable. Shall we > say its on the todo list. -Now that we have our test component setup we can test it's `format_number` +Now that we have our test component setup we can test its `format_number` method. To do this we put the test expression in a block followed by `on_client_to`. Again the block will be compiled using Opal, executed on the client, and the result will be returned to the expectation. diff --git a/docs/hyper-spec/03-hyperspec-methods-and-features.md b/docs/hyper-spec/03-hyperspec-methods-and-features.md index dc33a4434..448bd0d57 100644 --- a/docs/hyper-spec/03-hyperspec-methods-and-features.md +++ b/docs/hyper-spec/03-hyperspec-methods-and-features.md @@ -8,6 +8,7 @@ These can be used any where within your specs: + [`isomorphic`](#the-isomorphic-method) - executes code on the client *and* the server + [`mount`](#mounting-components) - mounts a hyperstack component in an empty window + [`before_mount`](#before_mount) - specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client` ++ [`insert_html`](#insert_html) - insert some html into a page + [`client_options`](#client-initialization-options) - allows options to be specified globally + [`run_on_client`](#run_on_client) - same as `on_client` but no value is returned + [`reload_page`](#reload_page) - resets the page environment @@ -50,7 +51,8 @@ in addition The following methods are used primarly at a debug break point, most require you use binding.pry as your debugger: -+ [`to_js`](#to_js) - returns the ruby code compiled to JS. ++ [`to_js`](#to_js) - returns the ruby code compiled to JS. ++ [`c?`](#c?) - alias for `on_client`. + [`ppr`](#ppr) - print the results of the ruby expression on the client console. + [`debugger`](#debugger) - Sets a debug breakpoint on code running on the client. + [`open_in_chrome`](#open_in_chrome) - Opens a chrome browser that will load the current state. @@ -65,7 +67,7 @@ DRIVER=chrome bundle exec rspec ### Timecop Integration -You can use the Timecop gem to control the flow of time within your specs. Hyperspec will coordinate things with the client so the time on the client is kept in sync with the time on the server. So for example if you use Timecop to advance time 1 day on the server, time on the browser will also advance by one day. +You can use the [`timecop` gem](https://github.com/travisjeffery/timecop) to control the flow of time within your specs. Hyperspec will coordinate things with the client so the time on the client is kept in sync with the time on the server. So for example if you use Timecop to advance time 1 day on the server, time on the browser will also advance by one day. See the [Client Initialization Options](#client-initialization-options) section for how to control the client time zone, and clock resolution. @@ -133,7 +135,7 @@ across blocks executed on the client. For example: end ``` -> Be especially careful of this this when using the [`no_reset`](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. +> Be especially careful of this when using the [`no_reset`](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. #### White and Black Listing Variables @@ -164,7 +166,15 @@ Examples: Note that the exclude_vars list will take precedence over the include_vars list. -The exclude/include lists can be overridden on an individual spec using the `with` method - See [Client Expectation Targets](#client-expectation-targets). +The exclude/include lists can be overridden on an individual call to on_client by providing a hash of names and values to on_client: + +```ruby + result = on_client(var: 12) { var * var } + expect(result).to eq(144) +``` + +You can do the same thing on expectations using the `with` method - See [Client Expectation Targets](#client-expectation-targets). + ### The `isomorphic` method @@ -196,7 +206,7 @@ Example: `client_option time_zone: 'Hawaii'` + `exclude_vars`: black list of all vars not to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. + `render_on`: `:client_only` (default), `:server_only`, or `:both` Hyperstack components can be prerendered on the server. The `render_on` option controls this feature. For example `server_only` is useful to insure components are properly prerendered. *See the `mount` method [below](#mounting-components) for more details on rendering components* -+ `no_wait`: After the page is loaded the system will by default wait until all javascript requests to the server complete, before proceeding. Specifying `no_wait: true` will skip this. ++ `no_wait`: After the page is loaded the system will by default wait until all javascript requests to the server complete before proceeding. Specifying `no_wait: true` will skip this. + `javascript`: The javascript asset to load when mounting the component. By default it will be `application` (.js is assumed). Note that the standard Hyperstack configuration will compile all the client side Ruby assets as well as javascript packages into the `application.js` file, so the default will work fine. + `style_sheet`: The style sheet asset to load when mounting the component. By default it will be `application` (.css is assumed). + `controller` - **(expert zone!)** specify a controller that will be used to mount the @@ -287,6 +297,12 @@ Specifies a block of code to be executed before the first call to `mount`, `isom > Unlike `mount`, `isomorphic` and `on_client`, `before_mount` does not load the client page, but will wait for the first of the other methods to be called. +#### `add_class` + +Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the React [style format.](https://reactjs.org/docs/dom-elements.html#style) + +Example: `add_class :some_class, borderStyle: :solid` adds a class with style `border-style: 'solid'` + #### `run_on_client` same as `on_client` but no value is returned. Useful when the return value may be too complex to marshall and unmarshall using JSON. @@ -295,12 +311,6 @@ same as `on_client` but no value is returned. Useful when the return value may Shorthand for `mount` with no parameters. Useful if you need to reset the client within a spec. -#### `add_class` - -Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the React [style format.](https://reactjs.org/docs/dom-elements.html#style) - -Example: `add_class :some_class, borderStyle: :solid` adds a class with style `border-style: 'solid'` - #### `size_window` Indicates the size of the browser window. The values can be given either symbolically or as two numbers (width and height). Predefined sizes are: @@ -323,6 +333,11 @@ So for example the following are all equivalent: returns any `ActiveModel` attributes loaded on the client. HyperModel will normally begin a load cycle as soon as you access the attribute on the client. However it is sometimes useful to see what attributes have already been loaded. +#### `insert_html` + +takes a string and inserts it into test page when it is mounted. Useful for testing code that is not dependent on Hyper Components. +For example an Opal library that adds some jQuery extensions. + ### Client Expectation Targets These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. @@ -370,6 +385,15 @@ By default HyperSpec will copy all local variables, memoized variables, and inst These methods are primarily designed to help debug code and specs. +#### `c?` + +Shorthand for `on_console`, useful for entering expressions in pry console, to investigate the state of the client. + +```ruby +pry:> c? { puts 'hello on the console' } # prints hello on the client +-> nil +``` + #### `to_js` Takes a block like `on_client` but rather than running the code on the client, simply returns the resulting code. This is useful for debugging obscure problems when the Opal compiler or some feature of @@ -391,15 +415,6 @@ This is useful when the result cannot be usefully returned to the server, or when the result of interest is better looked at as the raw javascript object. -#### `c?` - -Shorthand for `on_console`, useful for entering expressions in pry console, to investigate the state of the client. - -```ruby -pry:> c? { puts 'hello on the console' } # prints hello on the client --> nil -``` - #### `debugger` This psuedo method can be inserted into any code executed on the client. It will cause the code to stop, and enter a *javascript* read-eval loop, within the debug console. @@ -422,4 +437,4 @@ You can also run specs in a visible chrome window by setting the `DRIVER` enviro #### `pause` The method is typically not needed assuming you are using a multithreaded server like Puma. If for whatever reason the pry debug session is not multithreaded, *and* you want to try some kind of experiment on the javascript console, *and* those experiments make requests to the server, you may not get a response, because all threads are in use. -You can resolve this by using the `pause` method in the debug session which will put the debug session into a non-blocking loop. You then release the pause by executing `go()` in the *javascript* debug console. +You can resolve this by using the `pause` method in the debug session which will put the server debug session into a non-blocking loop. You can then experiment in the JS console, and when done release the pause by executing `go()` in the *javascript* debug console. diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb index 04d90a11e..380121be3 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb @@ -55,11 +55,13 @@ def build_test_url_for(controller = nil, ping = nil) def insure_page_loaded(only_if_code_or_html_exists = nil) return if only_if_code_or_html_exists && !@_hyperspec_private_client_code && !@_hyperspec_private_html_block + # if we are not resetting between examples, or think its mounted # then look for Opal, but if we can't find it, then ping to clear and try again if !HyperSpec.reset_between_examples? || page.instance_variable_get('@hyper_spec_mounted') r = evaluate_script('Opal && true') rescue nil return if r + page.visit build_test_url_for(nil, true) rescue nil end load_page diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index f9456502a..7d14a4f2c 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -2,8 +2,6 @@ describe 'hyper-spec', js: true do - # after(:each) { |e| binding.pry if e.exception } - it 'can visit a page' do visit 'test' end @@ -23,6 +21,14 @@ class ShowOff expect(page).to have_content('Now how cool is that???') end + it 'can add some html code before mounting' do + insert_html <<-HTML +
insert some code
+ HTML + mount + expect(page).to have_content('insert some code') + end + context "the client_option method" do it "can render server side only", :prerendering_on do From fd5e4ad17feab31611a3fbc0a020bc945b748d4c Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 20 Feb 2021 10:20:08 -0500 Subject: [PATCH 092/307] removed libv8 dependency --- ruby/hyper-spec/hyper-spec.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-spec/hyper-spec.gemspec b/ruby/hyper-spec/hyper-spec.gemspec index b86e9fce3..2efb449dc 100644 --- a/ruby/hyper-spec/hyper-spec.gemspec +++ b/ruby/hyper-spec/hyper-spec.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength spec.add_dependency 'capybara' spec.add_dependency 'chromedriver-helper', '1.2.0' spec.add_dependency 'filecache' - spec.add_dependency 'libv8', '~> 7.3.492.27.1' + # spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'method_source' spec.add_dependency 'opal', ENV['OPAL_VERSION'] || '>= 0.11.0', '< 2.0' spec.add_dependency 'parser', '>= 2.3.3.1' # on rails-6 this is now >= 2.3 From 260fea483a982bc114d02b17c4b76a20b1275039 Mon Sep 17 00:00:00 2001 From: Michail Date: Sat, 20 Feb 2021 17:43:50 +0200 Subject: [PATCH 093/307] correctly set react variant in production --- ruby/hyper-component/lib/react/react-source.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-component/lib/react/react-source.rb b/ruby/hyper-component/lib/react/react-source.rb index 23acdcf1d..cbe9b9f07 100644 --- a/ruby/hyper-component/lib/react/react-source.rb +++ b/ruby/hyper-component/lib/react/react-source.rb @@ -11,7 +11,7 @@ else require "hyperstack/internal/component" require "react/rails/asset_variant" - variant = Hyperstack.env.production? ? 'production' : 'development' - react_directory = React::Rails::AssetVariant.new({environment: variant}).react_directory + variant = Hyperstack.env.production? ? :production : :development + react_directory = React::Rails::AssetVariant.new({ variant: variant }).react_directory Opal.append_path react_directory.untaint end From 8052a7ea083315ebc695a641daedaa0a74e7fa66 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 20 Feb 2021 11:19:19 -0500 Subject: [PATCH 094/307] closes #353 --- .../batch1/policies/regulate_broadcast_spec.rb | 15 ++++++++++++++- .../lib/hyper-operation/transport/policy.rb | 11 ++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb b/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb index c5d412dc8..fc9e626a3 100644 --- a/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb +++ b/ruby/hyper-model/spec/batch1/policies/regulate_broadcast_spec.rb @@ -56,7 +56,7 @@ def saved_changes ) end - it "will raise an error if the policy is not sent" do + it "will raise an error if the to method is not used" do stub_const "TestModel1Policy", Class.new TestModel1Policy.class_eval do regulate_broadcast do | policy | @@ -68,6 +68,19 @@ def saved_changes to raise_error("TestModel1 instance broadcast policy not sent to any channel") end + it "will not raise an error if sending to the empty set" do + stub_const "TestModel1Policy", Class.new + TestModel1Policy.class_eval do + regulate_broadcast do | policy | + policy.send_all.to + end + end + model = TestModel1.new(id: 1, attr1: 1, attr2: 2, attr3: 3, attr4: 4, attr5: 5) + expect { |b| Hyperstack::InternalPolicy.regulate_broadcast(model, &b) }. + not_to raise_error("TestModel1 instance broadcast policy not sent to any channel") + end + + it "will intersect all policies for the same channel" do stub_const "TestModel1Policy", Class.new TestModel1Policy.class_eval do diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb b/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb index b7dc4c118..ee362d878 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/policy.rb @@ -328,9 +328,9 @@ def self.broadcast(instance, policy) regulations[instance].regulations.each do |regulation| instance.instance_exec wrap_policy(policy, regulation), ®ulation end - if policy.has_unassigned_sets? - raise "#{instance.class.name} instance broadcast policy not sent to any channel" - end + return if policy.has_to_been_called? + + raise "#{instance.class.name} instance broadcast policy not sent to any channel" end end @@ -425,12 +425,17 @@ def add_unassigned_send_set(send_set) end def send_set_to(send_set, channels) + @to_has_been_called = true channels.flatten(1).each do |channel| merge_set(send_set, channel) if channel_available? channel @unassigned_send_sets.delete(send_set) end end + def has_to_been_called? + !has_unassigned_sets? || @to_has_been_called + end + def merge_set(send_set, channel) return unless channel channel = channel.name if channel.is_a?(Class) && channel.name From a5ccc5a280874529257d8b535bebedb0b4654406 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 20 Feb 2021 12:46:59 -0500 Subject: [PATCH 095/307] fixing hyper-router dependency problem --- ruby/hyper-router/hyper-router.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-router/hyper-router.gemspec b/ruby/hyper-router/hyper-router.gemspec index a7447895c..2418fc684 100644 --- a/ruby/hyper-router/hyper-router.gemspec +++ b/ruby/hyper-router/hyper-router.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'hyper-spec', HyperRouter::VERSION spec.add_development_dependency 'hyper-store', HyperRouter::VERSION spec.add_development_dependency 'listen' - spec.add_development_dependency 'mini_racer', '~> 0.2.6' + spec.add_development_dependency 'mini_racer' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0.0' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'pry-stack_explorer' From 5bb9541584c7c28aabdec5fb00fe1a40c5bfc9c1 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 20 Feb 2021 14:33:51 -0500 Subject: [PATCH 096/307] closes #347 --- ruby/hyper-component/hyper-component.gemspec | 1 - .../lib/generators/hyperstack/install_generator.rb | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-component/hyper-component.gemspec b/ruby/hyper-component/hyper-component.gemspec index 4fd919aed..28c736877 100644 --- a/ruby/hyper-component/hyper-component.gemspec +++ b/ruby/hyper-component/hyper-component.gemspec @@ -23,7 +23,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'hyper-state', Hyperstack::Component::VERSION spec.add_dependency 'hyperstack-config', Hyperstack::Component::VERSION - # spec.add_dependency 'libv8', '~> 7.3.492.27.1' spec.add_dependency 'opal-activesupport', '~> 0.3.1' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb index c6e1c7529..9b6b5b15a 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb @@ -92,6 +92,7 @@ def add_webpacker_manifests def add_webpacks return if skip_webpack? + yarn 'react', '16' yarn 'react-dom', '16' yarn 'react-router', '^5.0.0' @@ -102,10 +103,15 @@ def add_webpacks end def cancel_react_source_import - return if skip_webpack? inject_into_initializer( - "Hyperstack.cancel_import 'react/react-source-browser' "\ - '# bring your own React and ReactRouter via Yarn/Webpacker' + if skip_webpack? + "Hyperstack.import 'react/react-source-browser' "\ + "# bring in hyperstack's copy of react, comment this out "\ + 'if you bring it in from webpacker' + else + "# Hyperstack.import 'react/react-source-browser' "\ + '# uncomment this line if you want hyperstack to use its copy of react' + end ) end From 7bf2bb1638c4c55abc3df5ff1ff0503ffb90da29 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 20 Feb 2021 14:53:53 -0500 Subject: [PATCH 097/307] removed hard dependency on Firefox driver --- ruby/hyper-spec/lib/hyper-spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 07f8a1927..2b9749689 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -203,7 +203,7 @@ def self.on_server? options = Selenium::WebDriver::Firefox::Options.new options.headless! Capybara::Selenium::Driver.new(app, browser: :firefox, options: options) - end + end if defined?(Selenium::WebDriver::Firefox) Capybara.register_driver :selenium_with_firebug do |app| profile = Selenium::WebDriver::Firefox::Profile.new @@ -211,7 +211,7 @@ def self.on_server? profile.enable_firebug options = Selenium::WebDriver::Firefox::Options.new(profile: profile) Capybara::Selenium::Driver.new(app, browser: :firefox, options: options) - end + end if defined?(Selenium::WebDriver::Firefox) Capybara.register_driver :safari do |app| Capybara::Selenium::Driver.new(app, browser: :safari) From 9e6a341a6602cd207a37c2a16ec160bf7939fa29 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 20 Feb 2021 15:01:57 -0500 Subject: [PATCH 098/307] still fixing the missing firefox problem --- ruby/hyper-spec/lib/hyper-spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 2b9749689..c64643980 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -23,7 +23,7 @@ require 'hyper-spec/expectations' require 'parser/current' -require 'selenium/web_driver/firefox/profile' +require 'selenium/web_driver/firefox/profile' if defined?(Selenium::WebDriver::Firefox) require 'selenium-webdriver' require 'hyper-spec/version' From aff85599827359205c0b2fc67cd1a7d6f9628820 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 14:44:16 -0500 Subject: [PATCH 099/307] first attempt a fix for #358 --- .../lib/reactive_record/active_record/instance_methods.rb | 3 --- ruby/hyper-model/lib/reactive_record/server_data_cache.rb | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb index 7b3969884..361b6844d 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb @@ -44,9 +44,6 @@ def method_missing(missing, *args, &block) end end - # ignore load_from_json when it calls _hyperstack_internal_setter_id - def _hyperstack_internal_setter_id(*); end - # the system assumes that there is "virtual" model_name and type attribute so # we define the internal setter here. If the user defines some other attributes # or uses these names no harm is done since the exact same method would have been diff --git a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb index 40b460098..e7f942953 100644 --- a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb +++ b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb @@ -474,7 +474,7 @@ def self.load_from_json(tree, target = nil) end end - if id_value = tree["id"] and id_value.is_a? Array + if (id_value = tree[target.class.try(:primary_key)]) && id_value.is_a?(Array) target.id = id_value.first end tree.each do |method, value| @@ -506,7 +506,7 @@ def self.load_from_json(tree, target = nil) target.send "#{method}=", value.first elsif value.is_a? Array - target.send("_hyperstack_internal_setter_#{method}", value.first) + target.send("_hyperstack_internal_setter_#{method}", value.first) unless method == target.class.primary_key elsif value.is_a?(Hash) && value[:id] && value[:id].first && (association = target.class.reflect_on_association(method)) # not sure if its necessary to check the id above... is it possible to for the method to be an association but not have an id? klass = value[:model_name] ? Object.const_get(value[:model_name].first) : association.klass From 0c9ecedc43be7dfb2248dca4cb9c2a3c0c59769a Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 17:30:21 -0500 Subject: [PATCH 100/307] fixed pause with argument --- ruby/hyper-spec/lib/hyper-spec/helpers.rb | 2 +- ruby/hyper-spec/spec/hyper_spec.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index b769d6146..8fc2b5185 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -187,7 +187,7 @@ def attributes_on_client(model) def pause(message = nil) if message puts message - page.evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" + internal_evaluate_ruby "puts #{message.inspect}.to_s + ' (type go() to continue)'" end page.evaluate_script('window.hyper_spec_waiting_for_go = true') diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 7d14a4f2c..90cd4e8a7 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -29,6 +29,17 @@ class ShowOff expect(page).to have_content('insert some code') end + it 'can pause the server', skip: 'unreliable' do + # this is pretty ugly with these dead waits, but any attempt to do an evaluate_script "go()" without the + # wait breaks + th = Thread.new { pause('hello') } + sleep 5 + expect(th).to be_alive + evaluate_script "go()" + sleep 1 + expect(th).not_to be_alive + end + context "the client_option method" do it "can render server side only", :prerendering_on do From a5ada4f43c91b1b48aa5a910c386cce0657ba39f Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 19:14:59 -0500 Subject: [PATCH 101/307] first cut adding json attribute types plus using postgresql on CI --- .travis.yml | 150 +++++++++--------- ruby/hyper-model/hyper-model.gemspec | 2 +- .../reactive_record/dummy_value.rb | 6 + .../batch1/column_types/column_type_spec.rb | 38 +++-- .../app/hyperstack/models/default_test.rb | 2 + .../app/hyperstack/models/type_test.rb | 4 +- .../spec/test_app/config/database.yml | 43 +++-- ruby/hyper-model/spec/test_app/db/schema.rb | 119 -------------- 8 files changed, 131 insertions(+), 233 deletions(-) delete mode 100644 ruby/hyper-model/spec/test_app/db/schema.rb diff --git a/.travis.yml b/.travis.yml index 9bee9afb7..10ee1e4ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +services: + - postgresql language: bash cache: bundler: true @@ -54,86 +56,86 @@ _deploy_gem: &_deploy_gem tags: true jobs: include: - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - <<: *_test_gem env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 - <<: *_test_gem env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - <<: *_deploy_gem env: COMPONENT=hyper-i18n diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 99c76614b..20e35b6b5 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -34,7 +34,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'factory_bot_rails' spec.add_development_dependency 'hyper-spec', HyperModel::VERSION spec.add_development_dependency 'mini_racer' - spec.add_development_dependency 'mysql2' + spec.add_development_dependency 'pg' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'pry-stack_explorer' diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index d025a6e35..6b2b24e7c 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -32,6 +32,12 @@ def build_default_value_for_nil @column_hash[:default] || nil end + def build_default_value_for_json + ::JSON.parse(@column_hash[:default]) if @column_hash[:default] + end + + alias build_default_value_for_jsonb build_default_value_for_json + def build_default_value_for_datetime if @column_hash[:default] ::Time.parse(@column_hash[:default].gsub(' ','T')+'+00:00') diff --git a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb index c6ad92680..7c54007d2 100644 --- a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb +++ b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb @@ -35,7 +35,7 @@ def as_json def time_only utc_time = Timex.new(self).utc start_time = Time.parse('2000-01-01T00:00:00.000-00:00').utc - Timex.new (start_time+(utc_time-utc_time.beginning_of_day.to_i).to_i).localtime + Timex.new(start_time+(utc_time-utc_time.beginning_of_day.to_i).to_i).localtime end class << self @@ -131,15 +131,17 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t + timestamp: t, + json: {kind: :json}, + jsonb: {kind: :jsonb} ) - expect_evaluate_ruby do + expect do TypeTest.columns_hash.collect do |attr, _info| TypeTest.find(1).send(attr).class end - end.to eq([ + end.to_on_client eq([ 'Number', 'NilClass', 'Boolean', 'Date', 'Time', 'Number', 'Number', 'Number', - 'Number', 'String', 'String', 'Time', 'Time' + 'Number', 'String', 'String', 'Time', 'Time', 'NilClass', 'NilClass' ]) check_errors end @@ -169,15 +171,15 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t + timestamp: t # see default tests below for json and jsonb ) - expect_evaluate_ruby do + expect do t = TypeTest.find(1) [ !t.boolean, t.date+1, t.datetime+2.days, t.decimal + 5, t.float + 6, t.integer + 7, t.bigint + 8, t.string.length, t.text.length, t.time+3.days, t.timestamp+4.days ] - end.to eq([ + end.on_client_to eq([ true, "2001-01-02", (Timex.sqlmin+2.days).as_json, 5, 6, 7, 8, 0, 0, (Timex.sqlmin+3.days).as_json, (Timex.sqlmin+4.days).as_json ]) @@ -197,15 +199,17 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t.time, - timestamp: t.time + timestamp: t.time, + json: {kind: :json}, + jsonb: {kind: :jsonb} ) - expect_promise do - ReactiveRecord.load do + expect do + Hyperstack::Model.load do TypeTest.columns_hash.collect do |attr, _info| [TypeTest.find(1).send(attr).class, TypeTest.find(1).send(attr)] end.flatten end - end.to eq([ + end.to_then eq([ 'Number', 1, 'NilClass', nil, 'Boolean', true, @@ -218,7 +222,9 @@ class DefaultTest < ActiveRecord::Base 'String', 'hello', 'String', 'goodby', 'Time', t.time_only.as_json, # date is indeterminate for active record time - 'Time', t.as_json + 'Time', t.as_json, + 'Hash', {'kind' => 'json'}, + 'Hash', {'kind' => 'jsonb'} ]) check_errors end @@ -302,12 +308,14 @@ class DefaultTest < ActiveRecord::Base [ t.string, t.date, t.datetime, t.integer_from_string, t.integer_from_int, t.float_from_string, t.float_from_float, - t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value + t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value, + t.json[:kind], t.jsonb[:kind] # the default for json and jsonb is nil so we will test dummy operations here ] end.to eq([ "I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json, 99, 98, 0.02, 0.01, - false, true, false + false, true, false, + 'json', 'jsonb' ]) check_errors end diff --git a/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb index f7433d6dc..82ce7fbd4 100644 --- a/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb @@ -11,6 +11,8 @@ def self.build_tables t.boolean :boolean_from_falsy_string, default: "OFF" t.boolean :boolean_from_truthy_string, default: "something-else" t.boolean :boolean_from_falsy_value, default: false + t.json :json, default: {kind: :json} + t.jsonb :jsonb, default: {kind: :jsonb} end end end diff --git a/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb index 86b319a72..50fc55d53 100644 --- a/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb @@ -13,6 +13,8 @@ def self.build_tables t.text(:text) t.time(:time) t.timestamp(:timestamp) + t.json(:json) + t.jsonb(:jsonb) end end -end \ No newline at end of file +end diff --git a/ruby/hyper-model/spec/test_app/config/database.yml b/ruby/hyper-model/spec/test_app/config/database.yml index ffe0fdf4f..ad8c9dd1c 100644 --- a/ruby/hyper-model/spec/test_app/config/database.yml +++ b/ruby/hyper-model/spec/test_app/config/database.yml @@ -4,10 +4,27 @@ # Ensure the SQLite 3 gem is defined in your Gemfile # gem 'sqlite3' # +# default: &default +# adapter: mysql2 +# encoding: utf8 +# username: root +# +# development: +# <<: *default +# database: hyper_mesh_development_db +# +# test: +# <<: *default +# database: hyper_mesh_test_db +# +# production: +# <<: *default +# database: hyper_mesh_production_db + default: &default - adapter: mysql2 - encoding: utf8 - username: root + adapter: postgresql + pool: 5 + timeout: 5000 development: <<: *default @@ -20,23 +37,3 @@ test: production: <<: *default database: hyper_mesh_production_db - -# default: &default -# adapter: sqlite3 -# pool: 5 -# timeout: 10000 -# -# development: -# <<: *default -# database: db/development.sqlite3 -# -# # Warning: The database defined as "test" will be erased and -# # re-generated from your development database when you run "rake". -# # Do not set this db to the same as development or production. -# test: -# <<: *default -# database: db/test.sqlite3 -# -# production: -# <<: *default -# database: db/production.sqlite3 diff --git a/ruby/hyper-model/spec/test_app/db/schema.rb b/ruby/hyper-model/spec/test_app/db/schema.rb deleted file mode 100644 index d5f44084b..000000000 --- a/ruby/hyper-model/spec/test_app/db/schema.rb +++ /dev/null @@ -1,119 +0,0 @@ -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 2016_07_31_182106) do - - create_table "addresses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "street" - t.string "city" - t.string "state" - t.string "zip" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "bones", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.integer "dog_id" - end - - create_table "child_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "child_attribute" - t.bigint "test_model_id" - t.index ["test_model_id"], name: "index_child_models_on_test_model_id" - end - - create_table "comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.text "comment" - t.datetime "created_at" - t.datetime "updated_at" - t.bigint "todo_id" - t.bigint "author_id" - t.integer "user_id" - t.integer "todo_item_id" - t.index ["author_id"], name: "index_comments_on_author_id" - t.index ["todo_id"], name: "index_comments_on_todo_id" - end - - create_table "hyperstack_connections", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "channel" - t.string "session" - t.datetime "created_at" - t.datetime "expires_at" - t.datetime "refresh_at" - end - - create_table "hyperstack_queued_messages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.text "data" - t.integer "connection_id" - end - - create_table "pets", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.integer "owner_id" - end - - create_table "scratching_posts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.integer "cat_id" - end - - create_table "test_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "test_attribute" - t.boolean "completed" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "todo_items", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "title" - t.text "description" - t.boolean "complete" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "user_id" - t.integer "comment_id" - end - - create_table "todos", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "title" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "completed", default: false, null: false - t.bigint "created_by_id" - t.bigint "owner_id" - t.index ["created_by_id"], name: "index_todos_on_created_by_id" - t.index ["owner_id"], name: "index_todos_on_owner_id" - end - - create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.string "role" - t.bigint "manager_id" - t.string "first_name" - t.string "last_name" - t.string "email" - t.datetime "created_at" - t.datetime "updated_at" - t.string "address_street" - t.string "address_city" - t.string "address_state" - t.string "address_zip" - t.integer "address_id" - t.string "address2_street" - t.string "address2_city" - t.string "address2_state" - t.string "address2_zip" - t.string "data_string" - t.integer "data_times" - t.integer "test_enum" - t.index ["manager_id"], name: "index_users_on_manager_id" - end - -end From a2d066fb032d240e70087e19649c0c7e36a77ae4 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 19:29:06 -0500 Subject: [PATCH 102/307] trying to get travis working --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 10ee1e4ce..975294295 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,7 @@ _test_gem: &_test_gem - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 + - psql -c 'create database hyper_mesh_test_db;' -U postgres - bundle exec rake spec:prepare - google-chrome --version - which google-chrome From 579b5438aa192031f8433bc5307258a5712b516c Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 19:37:22 -0500 Subject: [PATCH 103/307] trying to get travis working --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 975294295..93e60ba60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,3 @@ -services: - - postgresql language: bash cache: bundler: true @@ -8,6 +6,8 @@ cache: _test_gem: &_test_gem stage: test + services: + - postgresql addons: apt: sources: @@ -20,7 +20,7 @@ _test_gem: &_test_gem - google-chrome-stable - yarn - redis-server - mariadb: '10.3' + #mariadb: '10.3' before_install: - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package From 5385607df0dbd0f70722eda4afa4246ebaff1cc7 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 19:47:22 -0500 Subject: [PATCH 104/307] trying to get travis working --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 93e60ba60..aa50fc486 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,8 @@ _test_gem: &_test_gem - google-chrome-stable - yarn - redis-server - #mariadb: '10.3' + mariadb: '10.3' + postgresql: '11.2' before_install: - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package From fc0fb0c53ca1f4607fed9474b0cd24428180829f Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 20:57:17 -0500 Subject: [PATCH 105/307] trying to get travis working --- .travis.yml | 330 ++++++++++++++++++++++++++-------------------------- 1 file changed, 168 insertions(+), 162 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa50fc486..9557bbd21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,163 +1,169 @@ language: bash -cache: - bundler: true - directories: - - node_modules # NPM packages - -_test_gem: &_test_gem - stage: test - services: - - postgresql - addons: - apt: - sources: - - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' - packages: - - chromium-chromedriver - - google-chrome-stable - - yarn - - redis-server - mariadb: '10.3' - postgresql: '11.2' - before_install: - - echo installing $COMPONENT - # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package - # must remove this zombie for new yarn to work - - sudo rm -f /usr/local/bin/yarn - - nvm install 10 - - rvm install 2.6.3 # was 2.5.1 - - gem install bundler - - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - before_script: - - echo before_script $COMPONENT - - cd ruby/$COMPONENT - - bundle install --jobs=3 --retry=3 - - psql -c 'create database hyper_mesh_test_db;' -U postgres - - bundle exec rake spec:prepare - - google-chrome --version - - which google-chrome - - yarn install - script: - - echo running script $COMPONENT - - DRIVER=travis bundle exec rake $TASK - -_deploy_gem: &_deploy_gem - stage: release gems - before_script: - - cd ruby/$COMPONENT - script: - - echo deploying $COMPONENT - deploy: - - provider: rubygems - api_key: - secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" - on: - tags: true -jobs: - include: - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - - <<: *_deploy_gem - env: COMPONENT=hyper-i18n - - <<: *_deploy_gem - env: COMPONENT=hyper-trace - - <<: *_deploy_gem - env: COMPONENT=hyper-state - - <<: *_deploy_gem - env: COMPONENT=hyper-component - - <<: *_deploy_gem - env: COMPONENT=hyper-model - - <<: *_deploy_gem - env: COMPONENT=hyper-operation - - <<: *_deploy_gem - env: COMPONENT=hyper-router - - <<: *_deploy_gem - env: COMPONENT=hyper-spec - - <<: *_deploy_gem - env: COMPONENT=hyper-store - - <<: *_deploy_gem - env: COMPONENT=rails-hyperstack - - <<: *_deploy_gem - env: COMPONENT=hyperstack-config +# cache: +# bundler: true +# directories: +# - node_modules # NPM packages +services: + - postgresql +addons: + postgresql: '11.2' +before_script: + - psql -c 'create database hyper_mesh_test_db;' -U postgres +# +# _test_gem: &_test_gem +# stage: test +# services: +# - postgresql +# addons: +# apt: +# sources: +# - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' +# key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' +# - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' +# key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' +# packages: +# - chromium-chromedriver +# - google-chrome-stable +# - yarn +# - redis-server +# mariadb: '10.3' +# postgresql: '11.2' +# before_install: +# - echo installing $COMPONENT +# # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package +# # must remove this zombie for new yarn to work +# - sudo rm -f /usr/local/bin/yarn +# - nvm install 10 +# - rvm install 2.6.3 # was 2.5.1 +# - gem install bundler +# - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver +# before_script: +# - echo before_script $COMPONENT +# - cd ruby/$COMPONENT +# - bundle install --jobs=3 --retry=3 +# - psql -c 'create database hyper_mesh_test_db;' -U postgres +# - bundle exec rake spec:prepare +# - google-chrome --version +# - which google-chrome +# - yarn install +# script: +# - echo running script $COMPONENT +# - DRIVER=travis bundle exec rake $TASK +# +# _deploy_gem: &_deploy_gem +# stage: release gems +# before_script: +# - cd ruby/$COMPONENT +# script: +# - echo deploying $COMPONENT +# deploy: +# - provider: rubygems +# api_key: +# secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" +# on: +# tags: true +# jobs: +# include: +# # - <<: *_test_gem +# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 +# - <<: *_test_gem +# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 +# - <<: *_test_gem +# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 +# - <<: *_test_gem +# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 +# # +# # - <<: *_test_gem +# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # +# # - <<: *_test_gem +# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# +# - <<: *_deploy_gem +# env: COMPONENT=hyper-i18n +# - <<: *_deploy_gem +# env: COMPONENT=hyper-trace +# - <<: *_deploy_gem +# env: COMPONENT=hyper-state +# - <<: *_deploy_gem +# env: COMPONENT=hyper-component +# - <<: *_deploy_gem +# env: COMPONENT=hyper-model +# - <<: *_deploy_gem +# env: COMPONENT=hyper-operation +# - <<: *_deploy_gem +# env: COMPONENT=hyper-router +# - <<: *_deploy_gem +# env: COMPONENT=hyper-spec +# - <<: *_deploy_gem +# env: COMPONENT=hyper-store +# - <<: *_deploy_gem +# env: COMPONENT=rails-hyperstack +# - <<: *_deploy_gem +# env: COMPONENT=hyperstack-config From 81b8cfb31c6f0726e821b27c46600af57d65e350 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 21:04:54 -0500 Subject: [PATCH 106/307] trying to get travis working --- .travis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9557bbd21..c9886bfe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,17 @@ language: bash services: - postgresql addons: - postgresql: '11.2' + postgresql: "10" + apt: + packages: + - postgresql-10 + - postgresql-client-10 +env: + global: + - PGPORT=5433 before_script: - psql -c 'create database hyper_mesh_test_db;' -U postgres + # # _test_gem: &_test_gem # stage: test From 6fd5f9a2b5958ab5d9198c2b2ae40c4b3e253c74 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 21:09:12 -0500 Subject: [PATCH 107/307] trying to get travis working --- .travis.yml | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9886bfe7..e4aa48c9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,27 @@ -language: bash +#language: bash # cache: # bundler: true # directories: # - node_modules # NPM packages +# services: +# - postgresql +# addons: +# postgresql: "10" +# apt: +# packages: +# - postgresql-10 +# - postgresql-client-10 +# env: +# global: +# - PGPORT=5433 +# before_script: +# - psql -c 'create database hyper_mesh_test_db;' -U postgres services: - - postgresql + - postgresql + addons: - postgresql: "10" - apt: - packages: - - postgresql-10 - - postgresql-client-10 -env: - global: - - PGPORT=5433 + postgresql: '9.6' + before_script: - psql -c 'create database hyper_mesh_test_db;' -U postgres From 33f290e7945ca9149cc212a488ddd799cb1732d5 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 21:15:22 -0500 Subject: [PATCH 108/307] trying to get travis working --- .travis.yml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index e4aa48c9b..0701c7b9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,14 +16,31 @@ # - PGPORT=5433 # before_script: # - psql -c 'create database hyper_mesh_test_db;' -U postgres -services: - - postgresql - -addons: - postgresql: '9.6' +# services: +# - postgresql +# +# addons: +# postgresql: '9.6' +# +# before_script: +# - psql -c 'create database hyper_mesh_test_db;' -U postgres +before_install: + - sudo apt-get update + - sudo apt-get --yes remove postgresql\* + - sudo apt-get install -y postgresql-11 postgresql-client-11 + - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo service postgresql restart 11 before_script: - - psql -c 'create database hyper_mesh_test_db;' -U postgres + - psql --version + - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + # - cp config/database.yml.travis config/database.yml +# script: bundle exec rake spec +services: + - postgresql +addons: + postgresql: "11.2" # # _test_gem: &_test_gem From 2ac23cc4f7777d936d89e6d93c7807847b24a56c Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 22 Feb 2021 21:20:10 -0500 Subject: [PATCH 109/307] trying to get travis working --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0701c7b9f..6bb53ed34 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,9 @@ services: - postgresql addons: postgresql: "11.2" +env: + global: + - PGPORT=5433 # # _test_gem: &_test_gem From 5715da40907984e30c862b82c65bc74575473696 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 07:54:53 -0500 Subject: [PATCH 110/307] after getting a simple test to load pg, moving to running rspec --- .travis.yml | 379 ++++++++++++++++++++++++++-------------------------- 1 file changed, 187 insertions(+), 192 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6bb53ed34..0e6d8b7de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,9 @@ -#language: bash -# cache: -# bundler: true -# directories: -# - node_modules # NPM packages -# services: -# - postgresql -# addons: -# postgresql: "10" -# apt: -# packages: -# - postgresql-10 -# - postgresql-client-10 -# env: -# global: -# - PGPORT=5433 -# before_script: -# - psql -c 'create database hyper_mesh_test_db;' -U postgres +language: bash +cache: + bundler: true + directories: + - node_modules # NPM packages + # services: # - postgresql # @@ -25,181 +13,188 @@ # before_script: # - psql -c 'create database hyper_mesh_test_db;' -U postgres -before_install: - - sudo apt-get update - - sudo apt-get --yes remove postgresql\* - - sudo apt-get install -y postgresql-11 postgresql-client-11 - - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo service postgresql restart 11 -before_script: - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - # - cp config/database.yml.travis config/database.yml -# script: bundle exec rake spec -services: - - postgresql -addons: - postgresql: "11.2" +# before_install: +# - sudo apt-get update +# - sudo apt-get --yes remove postgresql\* +# - sudo apt-get install -y postgresql-11 postgresql-client-11 +# - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf +# - sudo service postgresql restart 11 +# before_script: +# - psql --version +# - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres +# - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres +# services: +# - postgresql +# addons: +# postgresql: "11.2" env: global: - PGPORT=5433 -# -# _test_gem: &_test_gem -# stage: test -# services: -# - postgresql -# addons: -# apt: -# sources: -# - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' -# key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' -# - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' -# key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' -# packages: -# - chromium-chromedriver -# - google-chrome-stable -# - yarn -# - redis-server -# mariadb: '10.3' -# postgresql: '11.2' -# before_install: -# - echo installing $COMPONENT -# # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package -# # must remove this zombie for new yarn to work -# - sudo rm -f /usr/local/bin/yarn -# - nvm install 10 -# - rvm install 2.6.3 # was 2.5.1 -# - gem install bundler -# - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver -# before_script: -# - echo before_script $COMPONENT -# - cd ruby/$COMPONENT -# - bundle install --jobs=3 --retry=3 -# - psql -c 'create database hyper_mesh_test_db;' -U postgres -# - bundle exec rake spec:prepare -# - google-chrome --version -# - which google-chrome -# - yarn install -# script: -# - echo running script $COMPONENT -# - DRIVER=travis bundle exec rake $TASK -# -# _deploy_gem: &_deploy_gem -# stage: release gems -# before_script: -# - cd ruby/$COMPONENT -# script: -# - echo deploying $COMPONENT -# deploy: -# - provider: rubygems -# api_key: -# secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" -# on: -# tags: true -# jobs: -# include: -# # - <<: *_test_gem -# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 -# - <<: *_test_gem -# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 -# - <<: *_test_gem -# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 -# - <<: *_test_gem -# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 -# # -# # - <<: *_test_gem -# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # -# # - <<: *_test_gem -# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# -# - <<: *_deploy_gem -# env: COMPONENT=hyper-i18n -# - <<: *_deploy_gem -# env: COMPONENT=hyper-trace -# - <<: *_deploy_gem -# env: COMPONENT=hyper-state -# - <<: *_deploy_gem -# env: COMPONENT=hyper-component -# - <<: *_deploy_gem -# env: COMPONENT=hyper-model -# - <<: *_deploy_gem -# env: COMPONENT=hyper-operation -# - <<: *_deploy_gem -# env: COMPONENT=hyper-router -# - <<: *_deploy_gem -# env: COMPONENT=hyper-spec -# - <<: *_deploy_gem -# env: COMPONENT=hyper-store -# - <<: *_deploy_gem -# env: COMPONENT=rails-hyperstack -# - <<: *_deploy_gem -# env: COMPONENT=hyperstack-config +_test_gem: &_test_gem + stage: test + services: + - postgresql + addons: + apt: + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + packages: + - chromium-chromedriver + - google-chrome-stable + - yarn + - redis-server + mariadb: '10.3' + postgresql: "11.2" + before_install: + - echo 'installing PG 11' + - sudo apt-get update + - sudo apt-get --yes remove postgresql\* + - sudo apt-get install -y postgresql-11 postgresql-client-11 + - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo service postgresql restart 11 + - echo installing $COMPONENT + # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package + # must remove this zombie for new yarn to work + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + before_script: + - echo creating pg database hyper_mesh_test_db + - psql --version + - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + - echo before_script $COMPONENT + - cd ruby/$COMPONENT + - bundle install --jobs=3 --retry=3 + - psql -c 'create database hyper_mesh_test_db;' -U postgres + - bundle exec rake spec:prepare + - google-chrome --version + - which google-chrome + - yarn install + script: + - echo running script $COMPONENT + - DRIVER=travis bundle exec rake $TASK + +_deploy_gem: &_deploy_gem + stage: release gems + before_script: + - cd ruby/$COMPONENT + script: + - echo deploying $COMPONENT + deploy: + - provider: rubygems + api_key: + secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" + on: + tags: true +jobs: + include: + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + + - <<: *_deploy_gem + env: COMPONENT=hyper-i18n + - <<: *_deploy_gem + env: COMPONENT=hyper-trace + - <<: *_deploy_gem + env: COMPONENT=hyper-state + - <<: *_deploy_gem + env: COMPONENT=hyper-component + - <<: *_deploy_gem + env: COMPONENT=hyper-model + - <<: *_deploy_gem + env: COMPONENT=hyper-operation + - <<: *_deploy_gem + env: COMPONENT=hyper-router + - <<: *_deploy_gem + env: COMPONENT=hyper-spec + - <<: *_deploy_gem + env: COMPONENT=hyper-store + - <<: *_deploy_gem + env: COMPONENT=rails-hyperstack + - <<: *_deploy_gem + env: COMPONENT=hyperstack-config From df255a2740f95e1feac030026c381eff27b3e361 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 08:03:26 -0500 Subject: [PATCH 111/307] before_install failed - moving to outer layer --- .travis.yml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e6d8b7de..d16f8b0e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,12 +13,12 @@ cache: # before_script: # - psql -c 'create database hyper_mesh_test_db;' -U postgres -# before_install: -# - sudo apt-get update -# - sudo apt-get --yes remove postgresql\* -# - sudo apt-get install -y postgresql-11 postgresql-client-11 -# - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf -# - sudo service postgresql restart 11 +before_install: + - sudo apt-get update + - sudo apt-get --yes remove postgresql\* + - sudo apt-get install -y postgresql-11 postgresql-client-11 + - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo service postgresql restart 11 # before_script: # - psql --version # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres @@ -50,12 +50,6 @@ _test_gem: &_test_gem mariadb: '10.3' postgresql: "11.2" before_install: - - echo 'installing PG 11' - - sudo apt-get update - - sudo apt-get --yes remove postgresql\* - - sudo apt-get install -y postgresql-11 postgresql-client-11 - - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo service postgresql restart 11 - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package # must remove this zombie for new yarn to work From 5a108713096124b33e15e6ad212f41352d771a73 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 08:24:29 -0500 Subject: [PATCH 112/307] before_install failed - moving more to outer layer --- .travis.yml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index d16f8b0e5..2c75c67b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,14 +19,15 @@ before_install: - sudo apt-get install -y postgresql-11 postgresql-client-11 - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - sudo service postgresql restart 11 -# before_script: -# - psql --version -# - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres -# - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres -# services: -# - postgresql -# addons: -# postgresql: "11.2" +before_script: + - echo creating pg database hyper_mesh_test_db + - psql --version + - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres +services: + - postgresql +addons: + postgresql: "11.2" env: global: - PGPORT=5433 @@ -48,7 +49,7 @@ _test_gem: &_test_gem - yarn - redis-server mariadb: '10.3' - postgresql: "11.2" + # postgresql: "11.2" before_install: - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package @@ -59,11 +60,11 @@ _test_gem: &_test_gem - gem install bundler - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver before_script: - - echo creating pg database hyper_mesh_test_db - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - - echo before_script $COMPONENT + # - echo creating pg database hyper_mesh_test_db + # - psql --version + # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + # - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + # - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - psql -c 'create database hyper_mesh_test_db;' -U postgres From 5909e654f615994218ec3f5e4e79f78ef1ae3498 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 09:05:01 -0500 Subject: [PATCH 113/307] install in the outer level restart in the inner --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c75c67b6..8dc1f1319 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,7 @@ _test_gem: &_test_gem mariadb: '10.3' # postgresql: "11.2" before_install: + - sudo service postgresql restart 11 - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package # must remove this zombie for new yarn to work @@ -60,11 +61,11 @@ _test_gem: &_test_gem - gem install bundler - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver before_script: - # - echo creating pg database hyper_mesh_test_db - # - psql --version - # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - # - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - # - echo before_script $COMPONENT + - echo creating pg database hyper_mesh_test_db + - psql --version + - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - psql -c 'create database hyper_mesh_test_db;' -U postgres From 9ec08065091f3c784cfac6f39a5d3bb571e80b51 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 09:19:11 -0500 Subject: [PATCH 114/307] why doesnt the cp of the config work --- .travis.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dc1f1319..3036942b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,17 +13,17 @@ cache: # before_script: # - psql -c 'create database hyper_mesh_test_db;' -U postgres -before_install: - - sudo apt-get update - - sudo apt-get --yes remove postgresql\* - - sudo apt-get install -y postgresql-11 postgresql-client-11 - - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo service postgresql restart 11 -before_script: - - echo creating pg database hyper_mesh_test_db - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres +# before_install: +# - sudo apt-get update +# - sudo apt-get --yes remove postgresql\* +# - sudo apt-get install -y postgresql-11 postgresql-client-11 +# - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf +# - sudo service postgresql restart 11 +# before_script: +# - echo creating pg database hyper_mesh_test_db +# - psql --version +# - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres +# - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres services: - postgresql addons: @@ -51,6 +51,12 @@ _test_gem: &_test_gem mariadb: '10.3' # postgresql: "11.2" before_install: + - sudo apt-get update + - sudo apt-get --yes remove postgresql\* + - sudo apt-get install -y postgresql-11 postgresql-client-11 + #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo ls /etc + - sudo ls /etc/postgresql/ - sudo service postgresql restart 11 - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package From 1caadadf7c934f2215583b8d3999297d10fdfbf2 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 09:35:28 -0500 Subject: [PATCH 115/307] save config before starting --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3036942b8..f3a133734 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,12 @@ cache: # before_script: # - psql -c 'create database hyper_mesh_test_db;' -U postgres -# before_install: +before_install: # - sudo apt-get update # - sudo apt-get --yes remove postgresql\* # - sudo apt-get install -y postgresql-11 postgresql-client-11 # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo cp /etc/postgresql/9.6/main/pg_hba.conf /etc/tmp/pga_hba.conf # - sudo service postgresql restart 11 # before_script: # - echo creating pg database hyper_mesh_test_db @@ -56,7 +57,8 @@ _test_gem: &_test_gem - sudo apt-get install -y postgresql-11 postgresql-client-11 #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - sudo ls /etc - - sudo ls /etc/postgresql/ + - sudo ls /etc/tmp + - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf - sudo service postgresql restart 11 - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package From 1957e9adc24f5a68f0a2a4616d22637e82862504 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 09:47:29 -0500 Subject: [PATCH 116/307] dump 9.6 config --- .travis.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3a133734..1e1f81e54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,22 +18,25 @@ before_install: # - sudo apt-get --yes remove postgresql\* # - sudo apt-get install -y postgresql-11 postgresql-client-11 # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo cp /etc/postgresql/9.6/main/pg_hba.conf /etc/tmp/pga_hba.conf + - sudo cat /etc/postgresql/9.6/main/pg_hba.conf # - sudo service postgresql restart 11 # before_script: # - echo creating pg database hyper_mesh_test_db # - psql --version # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres # - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres -services: - - postgresql -addons: - postgresql: "11.2" -env: - global: - - PGPORT=5433 +# services: +# - postgresql +# addons: +# postgresql: "11.2" +# env: +# global: +# - PGPORT=5433 _test_gem: &_test_gem + env: + global: + - PGPORT=5433 stage: test services: - postgresql @@ -50,15 +53,17 @@ _test_gem: &_test_gem - yarn - redis-server mariadb: '10.3' - # postgresql: "11.2" + postgresql: "11.2" before_install: - sudo apt-get update - sudo apt-get --yes remove postgresql\* - sudo apt-get install -y postgresql-11 postgresql-client-11 + - sudo cat /etc/postgresql/11/main/pg_hba.conf #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo ls /etc - - sudo ls /etc/tmp - - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf + # - sudo ls /etc + # - sudo ls /etc/tmp + # - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf + - sudo service postgresql restart 11 - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package From be12f1aafc4e82cfd0c723138a83a169fd680275 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 10:04:42 -0500 Subject: [PATCH 117/307] lets get it working with 9.6 --- .travis.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1e1f81e54..22523ade6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,9 +34,9 @@ before_install: # - PGPORT=5433 _test_gem: &_test_gem - env: - global: - - PGPORT=5433 + # env: + # global: + # - PGPORT=5433 stage: test services: - postgresql @@ -53,18 +53,18 @@ _test_gem: &_test_gem - yarn - redis-server mariadb: '10.3' - postgresql: "11.2" + postgresql: "9.6" before_install: - - sudo apt-get update - - sudo apt-get --yes remove postgresql\* - - sudo apt-get install -y postgresql-11 postgresql-client-11 - - sudo cat /etc/postgresql/11/main/pg_hba.conf - #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - # - sudo ls /etc - # - sudo ls /etc/tmp - # - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf - - - sudo service postgresql restart 11 + # - sudo apt-get update + # - sudo apt-get --yes remove postgresql\* + # - sudo apt-get install -y postgresql-11 postgresql-client-11 + # - sudo cat /etc/postgresql/11/main/pg_hba.conf + # #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + # # - sudo ls /etc + # # - sudo ls /etc/tmp + # # - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf + # + # - sudo service postgresql restart 11 - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package # must remove this zombie for new yarn to work From 3347f45a7fa5957750bd367de93b47521e1cc111 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 10:17:58 -0500 Subject: [PATCH 118/307] simple case with mariadb --- .travis.yml | 355 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 211 insertions(+), 144 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22523ade6..b4f73ed4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,36 +3,6 @@ cache: bundler: true directories: - node_modules # NPM packages - -# services: -# - postgresql -# -# addons: -# postgresql: '9.6' -# -# before_script: -# - psql -c 'create database hyper_mesh_test_db;' -U postgres - -before_install: -# - sudo apt-get update -# - sudo apt-get --yes remove postgresql\* -# - sudo apt-get install -y postgresql-11 postgresql-client-11 -# - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo cat /etc/postgresql/9.6/main/pg_hba.conf -# - sudo service postgresql restart 11 -# before_script: -# - echo creating pg database hyper_mesh_test_db -# - psql --version -# - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres -# - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres -# services: -# - postgresql -# addons: -# postgresql: "11.2" -# env: -# global: -# - PGPORT=5433 - _test_gem: &_test_gem # env: # global: @@ -52,22 +22,10 @@ _test_gem: &_test_gem - google-chrome-stable - yarn - redis-server - mariadb: '10.3' - postgresql: "9.6" + mariadb: '10.3' # mariadb works fine... + # postgresql: "9.6" before_install: - # - sudo apt-get update - # - sudo apt-get --yes remove postgresql\* - # - sudo apt-get install -y postgresql-11 postgresql-client-11 - # - sudo cat /etc/postgresql/11/main/pg_hba.conf - # #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - # # - sudo ls /etc - # # - sudo ls /etc/tmp - # # - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf - # - # - sudo service postgresql restart 11 - echo installing $COMPONENT - # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package - # must remove this zombie for new yarn to work - sudo rm -f /usr/local/bin/yarn - nvm install 10 - rvm install 2.6.3 # was 2.5.1 @@ -104,106 +62,215 @@ _deploy_gem: &_deploy_gem tags: true jobs: include: - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_deploy_gem - env: COMPONENT=hyper-i18n - - <<: *_deploy_gem - env: COMPONENT=hyper-trace - - <<: *_deploy_gem - env: COMPONENT=hyper-state - - <<: *_deploy_gem - env: COMPONENT=hyper-component - - <<: *_deploy_gem - env: COMPONENT=hyper-model - - <<: *_deploy_gem - env: COMPONENT=hyper-operation - - <<: *_deploy_gem - env: COMPONENT=hyper-router - - <<: *_deploy_gem - env: COMPONENT=hyper-spec - - <<: *_deploy_gem - env: COMPONENT=hyper-store - - <<: *_deploy_gem - env: COMPONENT=rails-hyperstack - - <<: *_deploy_gem - env: COMPONENT=hyperstack-config +# language: bash +# cache: +# bundler: true +# directories: +# - node_modules # NPM packages +# +# # services: +# # - postgresql +# # +# # addons: +# # postgresql: '9.6' +# # +# # before_script: +# # - psql -c 'create database hyper_mesh_test_db;' -U postgres +# +# before_install: +# # - sudo apt-get update +# # - sudo apt-get --yes remove postgresql\* +# # - sudo apt-get install -y postgresql-11 postgresql-client-11 +# # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf +# - sudo cat /etc/postgresql/9.6/main/pg_hba.conf +# # - sudo service postgresql restart 11 +# # before_script: +# # - echo creating pg database hyper_mesh_test_db +# # - psql --version +# # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres +# # - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres +# # services: +# # - postgresql +# # addons: +# # postgresql: "11.2" +# # env: +# # global: +# # - PGPORT=5433 +# +# _test_gem: &_test_gem +# # env: +# # global: +# # - PGPORT=5433 +# stage: test +# services: +# - postgresql +# addons: +# apt: +# sources: +# - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' +# key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' +# - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' +# key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' +# packages: +# - chromium-chromedriver +# - google-chrome-stable +# - yarn +# - redis-server +# mariadb: '10.3' +# postgresql: "9.6" +# before_install: +# # - sudo apt-get update +# # - sudo apt-get --yes remove postgresql\* +# # - sudo apt-get install -y postgresql-11 postgresql-client-11 +# # - sudo cat /etc/postgresql/11/main/pg_hba.conf +# # #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf +# # # - sudo ls /etc +# # # - sudo ls /etc/tmp +# # # - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf +# # +# # - sudo service postgresql restart 11 +# - echo installing $COMPONENT +# # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package +# # must remove this zombie for new yarn to work +# - sudo rm -f /usr/local/bin/yarn +# - nvm install 10 +# - rvm install 2.6.3 # was 2.5.1 +# - gem install bundler +# - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver +# before_script: +# - echo creating pg database hyper_mesh_test_db +# - psql --version +# - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres +# - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres +# - echo before_script $COMPONENT +# - cd ruby/$COMPONENT +# - bundle install --jobs=3 --retry=3 +# - psql -c 'create database hyper_mesh_test_db;' -U postgres +# - bundle exec rake spec:prepare +# - google-chrome --version +# - which google-chrome +# - yarn install +# script: +# - echo running script $COMPONENT +# - DRIVER=travis bundle exec rake $TASK +# +# _deploy_gem: &_deploy_gem +# stage: release gems +# before_script: +# - cd ruby/$COMPONENT +# script: +# - echo deploying $COMPONENT +# deploy: +# - provider: rubygems +# api_key: +# secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" +# on: +# tags: true +# jobs: +# include: +# # - <<: *_test_gem +# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 +# - <<: *_test_gem +# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 +# - <<: *_test_gem +# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 +# - <<: *_test_gem +# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 +# # +# # - <<: *_test_gem +# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' +# # +# # - <<: *_test_gem +# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 +# # - <<: *_test_gem +# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' +# +# - <<: *_deploy_gem +# env: COMPONENT=hyper-i18n +# - <<: *_deploy_gem +# env: COMPONENT=hyper-trace +# - <<: *_deploy_gem +# env: COMPONENT=hyper-state +# - <<: *_deploy_gem +# env: COMPONENT=hyper-component +# - <<: *_deploy_gem +# env: COMPONENT=hyper-model +# - <<: *_deploy_gem +# env: COMPONENT=hyper-operation +# - <<: *_deploy_gem +# env: COMPONENT=hyper-router +# - <<: *_deploy_gem +# env: COMPONENT=hyper-spec +# - <<: *_deploy_gem +# env: COMPONENT=hyper-store +# - <<: *_deploy_gem +# env: COMPONENT=rails-hyperstack +# - <<: *_deploy_gem +# env: COMPONENT=hyperstack-config From 7aee7beccf2502366dcc219c1fac2f762dbbad33 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 10:36:59 -0500 Subject: [PATCH 119/307] simple case with mariadb round 2 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b4f73ed4d..e64608b69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,9 +33,9 @@ _test_gem: &_test_gem - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver before_script: - echo creating pg database hyper_mesh_test_db - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + # - psql --version + # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + # - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 From c2ae281ae3cbaffc9580d84ca208a8c586c84aa9 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 10:49:09 -0500 Subject: [PATCH 120/307] simple case with mariadb round 3 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e64608b69..383a31650 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ _test_gem: &_test_gem - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - - psql -c 'create database hyper_mesh_test_db;' -U postgres + # - psql -c 'create database hyper_mesh_test_db;' -U postgres - bundle exec rake spec:prepare - google-chrome --version - which google-chrome From bba3437e8cf6218d88ae9b7c69b897a0b3a167a3 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 10:56:10 -0500 Subject: [PATCH 121/307] simple case with postgresql --- .travis.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 383a31650..a9adfd625 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,8 @@ _test_gem: &_test_gem - google-chrome-stable - yarn - redis-server - mariadb: '10.3' # mariadb works fine... - # postgresql: "9.6" + # mariadb: '10.3' # mariadb works fine... + postgresql: "9.6" before_install: - echo installing $COMPONENT - sudo rm -f /usr/local/bin/yarn @@ -33,13 +33,12 @@ _test_gem: &_test_gem - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver before_script: - echo creating pg database hyper_mesh_test_db - # - psql --version - # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - # - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + - psql --version + - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - # - psql -c 'create database hyper_mesh_test_db;' -U postgres - bundle exec rake spec:prepare - google-chrome --version - which google-chrome From 91998e0e0e72139b553a1a39f7be88f1e8105812 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 11:07:31 -0500 Subject: [PATCH 122/307] trying a different way to set pg port --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a9adfd625..01a7e29a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,7 +62,7 @@ _deploy_gem: &_deploy_gem jobs: include: - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 + env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 # language: bash # cache: From e2d8be4fd75af4210e97da5ec8403a5aeaeab4f2 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 22:32:34 -0500 Subject: [PATCH 123/307] tweaks to primary_key and trying another round of postgresql --- .travis.yml | 2 ++ .../lib/reactive_record/active_record/class_methods.rb | 2 +- .../reactive_record/active_record/instance_methods.rb | 2 +- ruby/hyper-model/lib/reactive_record/broadcast.rb | 10 +++++----- .../lib/reactive_record/server_data_cache.rb | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01a7e29a5..7e26ffb08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ _test_gem: &_test_gem # mariadb: '10.3' # mariadb works fine... postgresql: "9.6" before_install: + # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo ls /etc/postgresql/ - echo installing $COMPONENT - sudo rm -f /usr/local/bin/yarn - nvm install 10 diff --git a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb index f9daab46a..dd467d728 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb @@ -364,7 +364,7 @@ def server_method(name, default: nil) def define_attribute_methods columns_hash.each do |name, column_hash| - next if name == primary_key + next if name == :id # only add serialized key if its serialized. This just makes testing a bit # easier by keeping the columns_hash the same if there are no seralized strings # see rspec ./spec/batch1/column_types/column_type_spec.rb:100 diff --git a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb index 361b6844d..453ca8124 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb @@ -92,7 +92,7 @@ def initialize(hash = {}) end self.class.load_data do h.each do |attribute, value| - next if attribute == primary_key + next if attribute == :id @ar_instance[attribute] = value changed_attributes << attribute end diff --git a/ruby/hyper-model/lib/reactive_record/broadcast.rb b/ruby/hyper-model/lib/reactive_record/broadcast.rb index 7fff8a1fb..699ed9f3e 100644 --- a/ruby/hyper-model/lib/reactive_record/broadcast.rb +++ b/ruby/hyper-model/lib/reactive_record/broadcast.rb @@ -88,7 +88,7 @@ def self.to_self(record, data = {}) def record_with_current_values ReactiveRecord::Base.load_data do - backing_record = @backing_record || klass.find(record[:id]).backing_record + backing_record = @backing_record || klass.find(record[klass.primary_key]).backing_record if destroyed? backing_record.ar_instance else @@ -148,7 +148,7 @@ def local(operation, record, data) @klass = record.class.name @record = data record.backing_record.destroyed = false - @record[:id] = record.id if record.id + @record[@klass.primary_key] = record.id if record.id record.backing_record.destroyed = @destroyed @backing_record = record.backing_record @previous_changes = record.changes @@ -163,7 +163,7 @@ def receive(params) @record.merge! params.record @previous_changes.merge! params.previous_changes ReactiveRecord::Base.when_not_saving(klass) do - @backing_record = ReactiveRecord::Base.exists?(klass, params.record[:id]) + @backing_record = ReactiveRecord::Base.exists?(klass, params.record[klass.primary_key]) # first check to see if we already destroyed it and if so exit the block return if @backing_record&.destroyed @@ -180,7 +180,7 @@ def receive(params) # it is possible that we are recieving data on a record for which we are also waiting # on an an inital data load in which case we have not yet set the loaded id, so we # set if now. - @backing_record&.loaded_id = params.record[:id] + @backing_record&.loaded_id = params.record[klass.primary_key] # once we have received all the data from all the channels (applies to create and update only) # we yield and process the record @@ -234,7 +234,7 @@ def process_previous_changes def merge_current_values(br) current_values = Hash[*@previous_changes.collect do |attr, values| - value = attr == :id ? record[:id] : values.first + value = attr == klass.primary_key ? record[klass.primary_key] : values.first if br.attributes.key?(attr) && br.attributes[attr] != br.convert(attr, value) && br.attributes[attr] != br.convert(attr, values.last) diff --git a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb index e7f942953..002075915 100644 --- a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb +++ b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb @@ -474,7 +474,7 @@ def self.load_from_json(tree, target = nil) end end - if (id_value = tree[target.class.try(:primary_key)]) && id_value.is_a?(Array) + if (id_value = tree[target.class.try(:primary_key)] || tree[:id]) && id_value.is_a?(Array) target.id = id_value.first end tree.each do |method, value| From 3f15072d55579f0041c1c89666de08b57fd1de2f Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 22:57:57 -0500 Subject: [PATCH 124/307] back to getting base config for mysql --- .travis.yml | 132 ++++++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e26ffb08..f8126dc8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,70 +1,70 @@ -language: bash -cache: - bundler: true - directories: - - node_modules # NPM packages -_test_gem: &_test_gem - # env: - # global: - # - PGPORT=5433 - stage: test - services: - - postgresql - addons: - apt: - sources: - - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' - packages: - - chromium-chromedriver - - google-chrome-stable - - yarn - - redis-server - # mariadb: '10.3' # mariadb works fine... - postgresql: "9.6" - before_install: - # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo ls /etc/postgresql/ - - echo installing $COMPONENT - - sudo rm -f /usr/local/bin/yarn - - nvm install 10 - - rvm install 2.6.3 # was 2.5.1 - - gem install bundler - - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - before_script: - - echo creating pg database hyper_mesh_test_db - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - - echo before_script $COMPONENT - - cd ruby/$COMPONENT - - bundle install --jobs=3 --retry=3 - - bundle exec rake spec:prepare - - google-chrome --version - - which google-chrome - - yarn install - script: - - echo running script $COMPONENT - - DRIVER=travis bundle exec rake $TASK +# language: bash +# cache: +# bundler: true +# directories: +# - node_modules # NPM packages +# _test_gem: &_test_gem +# # env: +# # global: +# # - PGPORT=5433 +# stage: test +services: + - postgresql +addons: + apt: + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + packages: + - chromium-chromedriver + - google-chrome-stable + - yarn + - redis-server + # mariadb: '10.3' # mariadb works fine... + postgresql: "9.6" +before_install: + # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo ls /etc/postgresql/ + - echo installing $COMPONENT + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver +before_script: + - echo creating pg database hyper_mesh_test_db + - psql --version + - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + - echo before_script $COMPONENT + - cd ruby/$COMPONENT + - bundle install --jobs=3 --retry=3 + - bundle exec rake spec:prepare + - google-chrome --version + - which google-chrome + - yarn install +script: + - echo running script $COMPONENT + - DRIVER=travis bundle exec rake $TASK -_deploy_gem: &_deploy_gem - stage: release gems - before_script: - - cd ruby/$COMPONENT - script: - - echo deploying $COMPONENT - deploy: - - provider: rubygems - api_key: - secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" - on: - tags: true -jobs: - include: - - <<: *_test_gem - env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 +# _deploy_gem: &_deploy_gem +# stage: release gems +# before_script: +# - cd ruby/$COMPONENT +# script: +# - echo deploying $COMPONENT +# deploy: +# - provider: rubygems +# api_key: +# secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" +# on: +# tags: true +# jobs: +# include: +# - <<: *_test_gem +# env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 # language: bash # cache: From e7494ca9f9ccd95b32f1c291c8dd780f3a382c58 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 23:07:13 -0500 Subject: [PATCH 125/307] trying to get config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f8126dc8e..6def0f8c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ addons: postgresql: "9.6" before_install: # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo ls /etc/postgresql/ + - sudo cat /etc/postgresql/10/main/pg_hba.conf - echo installing $COMPONENT - sudo rm -f /usr/local/bin/yarn - nvm install 10 From 32c2c8837e084d291b4603d75f76ee781efdbc5b Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 23:19:59 -0500 Subject: [PATCH 126/307] now trying to get 11 config --- .travis.yml | 135 +++++++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6def0f8c7..d78cdecd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,70 +1,73 @@ -# language: bash -# cache: -# bundler: true -# directories: -# - node_modules # NPM packages -# _test_gem: &_test_gem -# # env: -# # global: -# # - PGPORT=5433 -# stage: test -services: - - postgresql -addons: - apt: - sources: - - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' - packages: - - chromium-chromedriver - - google-chrome-stable - - yarn - - redis-server - # mariadb: '10.3' # mariadb works fine... - postgresql: "9.6" -before_install: - # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo cat /etc/postgresql/10/main/pg_hba.conf - - echo installing $COMPONENT - - sudo rm -f /usr/local/bin/yarn - - nvm install 10 - - rvm install 2.6.3 # was 2.5.1 - - gem install bundler - - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver -before_script: - - echo creating pg database hyper_mesh_test_db - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - - echo before_script $COMPONENT - - cd ruby/$COMPONENT - - bundle install --jobs=3 --retry=3 - - bundle exec rake spec:prepare - - google-chrome --version - - which google-chrome - - yarn install -script: - - echo running script $COMPONENT - - DRIVER=travis bundle exec rake $TASK +language: bash +cache: + bundler: true + directories: + - node_modules # NPM packages +_test_gem: &_test_gem + # env: + # global: + # - PGPORT=5433 + stage: test + services: + - postgresql + addons: + apt: + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + packages: + - chromium-chromedriver + - google-chrome-stable + - yarn + - redis-server + # mariadb: '10.3' # mariadb works fine... + postgresql: "11.2" + before_install: + - sudo apt-get update + - sudo apt-get --yes remove postgresql\* + - sudo apt-get install -y postgresql-11 postgresql-client-11 + # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo cat /etc/postgressql/11/main/pg_hba.conf + - echo installing $COMPONENT + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + before_script: + - echo creating pg database hyper_mesh_test_db + - psql --version + - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres + - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres + - echo before_script $COMPONENT + - cd ruby/$COMPONENT + - bundle install --jobs=3 --retry=3 + - bundle exec rake spec:prepare + - google-chrome --version + - which google-chrome + - yarn install + script: + - echo running script $COMPONENT + - DRIVER=travis bundle exec rake $TASK -# _deploy_gem: &_deploy_gem -# stage: release gems -# before_script: -# - cd ruby/$COMPONENT -# script: -# - echo deploying $COMPONENT -# deploy: -# - provider: rubygems -# api_key: -# secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" -# on: -# tags: true -# jobs: -# include: -# - <<: *_test_gem -# env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 +_deploy_gem: &_deploy_gem + stage: release gems + before_script: + - cd ruby/$COMPONENT + script: + - echo deploying $COMPONENT + deploy: + - provider: rubygems + api_key: + secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" + on: + tags: true +jobs: + include: + - <<: *_test_gem + env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 # language: bash # cache: From cf29e695cf8484288e91d68ada7acdf5e747d6b3 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 23 Feb 2021 23:49:11 -0500 Subject: [PATCH 127/307] now trying to get 11 config 2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d78cdecd0..53534a09b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ _test_gem: &_test_gem - sudo apt-get --yes remove postgresql\* - sudo apt-get install -y postgresql-11 postgresql-client-11 # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo cat /etc/postgressql/11/main/pg_hba.conf + - sudo cat /etc/postgresql/11/main/pg_hba.conf - echo installing $COMPONENT - sudo rm -f /usr/local/bin/yarn - nvm install 10 From e0787e7c45310c3f5dd6084d6c0ef3b0b2f466bb Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 24 Feb 2021 10:33:01 -0500 Subject: [PATCH 128/307] trying to create the config file --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 53534a09b..5043fec8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,12 +22,19 @@ _test_gem: &_test_gem - google-chrome-stable - yarn - redis-server - # mariadb: '10.3' # mariadb works fine... + mariadb: '10.3' # mariadb works fine... postgresql: "11.2" before_install: - sudo apt-get update - sudo apt-get --yes remove postgresql\* - sudo apt-get install -y postgresql-11 postgresql-client-11 + - | + cat </etc/postgresql/11/main/pg_hba.conf + local all postgres trust + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + EOF # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - sudo cat /etc/postgresql/11/main/pg_hba.conf - echo installing $COMPONENT From 8cb246d17a66968fdc377df58a51010498b62077 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 24 Feb 2021 10:41:26 -0500 Subject: [PATCH 129/307] trying to create the config file 2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5043fec8f..c552dec9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ _test_gem: &_test_gem - sudo apt-get --yes remove postgresql\* - sudo apt-get install -y postgresql-11 postgresql-client-11 - | - cat </etc/postgresql/11/main/pg_hba.conf + sudo cat </etc/postgresql/11/main/pg_hba.conf local all postgres trust local all all trust host all all 127.0.0.1/32 trust From a55b56f6a570960e5306ab8682e21731204b0c11 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 24 Feb 2021 10:50:45 -0500 Subject: [PATCH 130/307] trying to create the config file 3 --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index c552dec9b..29a91d3cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,12 +29,12 @@ _test_gem: &_test_gem - sudo apt-get --yes remove postgresql\* - sudo apt-get install -y postgresql-11 postgresql-client-11 - | - sudo cat </etc/postgresql/11/main/pg_hba.conf - local all postgres trust - local all all trust - host all all 127.0.0.1/32 trust - host all all ::1/128 trust - EOF + sudo cat </etc/postgresql/11/main/pg_hba.conf + local all postgres trust + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + EOF # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - sudo cat /etc/postgresql/11/main/pg_hba.conf - echo installing $COMPONENT From 46b7e04c992ddafdb623e3fb84468e05d6c6a4e6 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 24 Feb 2021 16:18:52 -0500 Subject: [PATCH 131/307] reverted changes to do with postgresql --- .travis.yml | 342 ++++++------------ ruby/hyper-model/hyper-model.gemspec | 2 +- .../batch1/column_types/column_type_spec.rb | 22 +- .../app/hyperstack/models/default_test.rb | 2 - .../app/hyperstack/models/type_test.rb | 2 - .../spec/test_app/config/database.yml | 28 +- 6 files changed, 118 insertions(+), 280 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29a91d3cc..e852a5744 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,11 @@ -language: bash +language: bash cache: bundler: true directories: - - node_modules # NPM packages + - node_modules # NPM packages + _test_gem: &_test_gem - # env: - # global: - # - PGPORT=5433 stage: test - services: - - postgresql addons: apt: sources: @@ -22,32 +18,17 @@ _test_gem: &_test_gem - google-chrome-stable - yarn - redis-server - mariadb: '10.3' # mariadb works fine... - postgresql: "11.2" + mariadb: '10.3' before_install: - - sudo apt-get update - - sudo apt-get --yes remove postgresql\* - - sudo apt-get install -y postgresql-11 postgresql-client-11 - - | - sudo cat </etc/postgresql/11/main/pg_hba.conf - local all postgres trust - local all all trust - host all all 127.0.0.1/32 trust - host all all ::1/128 trust - EOF - # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo cat /etc/postgresql/11/main/pg_hba.conf - echo installing $COMPONENT + # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package + # must remove this zombie for new yarn to work - sudo rm -f /usr/local/bin/yarn - nvm install 10 - rvm install 2.6.3 # was 2.5.1 - gem install bundler - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver before_script: - - echo creating pg database hyper_mesh_test_db - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 @@ -74,214 +55,105 @@ _deploy_gem: &_deploy_gem jobs: include: - <<: *_test_gem - env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# language: bash -# cache: -# bundler: true -# directories: -# - node_modules # NPM packages -# -# # services: -# # - postgresql -# # -# # addons: -# # postgresql: '9.6' -# # -# # before_script: -# # - psql -c 'create database hyper_mesh_test_db;' -U postgres -# -# before_install: -# # - sudo apt-get update -# # - sudo apt-get --yes remove postgresql\* -# # - sudo apt-get install -y postgresql-11 postgresql-client-11 -# # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf -# - sudo cat /etc/postgresql/9.6/main/pg_hba.conf -# # - sudo service postgresql restart 11 -# # before_script: -# # - echo creating pg database hyper_mesh_test_db -# # - psql --version -# # - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres -# # - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres -# # services: -# # - postgresql -# # addons: -# # postgresql: "11.2" -# # env: -# # global: -# # - PGPORT=5433 -# -# _test_gem: &_test_gem -# # env: -# # global: -# # - PGPORT=5433 -# stage: test -# services: -# - postgresql -# addons: -# apt: -# sources: -# - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' -# key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' -# - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' -# key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' -# packages: -# - chromium-chromedriver -# - google-chrome-stable -# - yarn -# - redis-server -# mariadb: '10.3' -# postgresql: "9.6" -# before_install: -# # - sudo apt-get update -# # - sudo apt-get --yes remove postgresql\* -# # - sudo apt-get install -y postgresql-11 postgresql-client-11 -# # - sudo cat /etc/postgresql/11/main/pg_hba.conf -# # #- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf -# # # - sudo ls /etc -# # # - sudo ls /etc/tmp -# # # - sudo cp /etc/tmp/pg_hba.conf /etc/postgresql/11/main/pg_hba.conf -# # -# # - sudo service postgresql restart 11 -# - echo installing $COMPONENT -# # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package -# # must remove this zombie for new yarn to work -# - sudo rm -f /usr/local/bin/yarn -# - nvm install 10 -# - rvm install 2.6.3 # was 2.5.1 -# - gem install bundler -# - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver -# before_script: -# - echo creating pg database hyper_mesh_test_db -# - psql --version -# - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres -# - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres -# - echo before_script $COMPONENT -# - cd ruby/$COMPONENT -# - bundle install --jobs=3 --retry=3 -# - psql -c 'create database hyper_mesh_test_db;' -U postgres -# - bundle exec rake spec:prepare -# - google-chrome --version -# - which google-chrome -# - yarn install -# script: -# - echo running script $COMPONENT -# - DRIVER=travis bundle exec rake $TASK -# -# _deploy_gem: &_deploy_gem -# stage: release gems -# before_script: -# - cd ruby/$COMPONENT -# script: -# - echo deploying $COMPONENT -# deploy: -# - provider: rubygems -# api_key: -# secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" -# on: -# tags: true -# jobs: -# include: -# # - <<: *_test_gem -# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 -# - <<: *_test_gem -# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 -# - <<: *_test_gem -# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 -# - <<: *_test_gem -# env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 -# # -# # - <<: *_test_gem -# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' -# # -# # - <<: *_test_gem -# # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 -# # - <<: *_test_gem -# # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' -# -# - <<: *_deploy_gem -# env: COMPONENT=hyper-i18n -# - <<: *_deploy_gem -# env: COMPONENT=hyper-trace -# - <<: *_deploy_gem -# env: COMPONENT=hyper-state -# - <<: *_deploy_gem -# env: COMPONENT=hyper-component -# - <<: *_deploy_gem -# env: COMPONENT=hyper-model -# - <<: *_deploy_gem -# env: COMPONENT=hyper-operation -# - <<: *_deploy_gem -# env: COMPONENT=hyper-router -# - <<: *_deploy_gem -# env: COMPONENT=hyper-spec -# - <<: *_deploy_gem -# env: COMPONENT=hyper-store -# - <<: *_deploy_gem -# env: COMPONENT=rails-hyperstack -# - <<: *_deploy_gem -# env: COMPONENT=hyperstack-config + - <<: *_deploy_gem + env: COMPONENT=hyper-i18n + - <<: *_deploy_gem + env: COMPONENT=hyper-trace + - <<: *_deploy_gem + env: COMPONENT=hyper-state + - <<: *_deploy_gem + env: COMPONENT=hyper-component + - <<: *_deploy_gem + env: COMPONENT=hyper-model + - <<: *_deploy_gem + env: COMPONENT=hyper-operation + - <<: *_deploy_gem + env: COMPONENT=hyper-router + - <<: *_deploy_gem + env: COMPONENT=hyper-spec + - <<: *_deploy_gem + env: COMPONENT=hyper-store + - <<: *_deploy_gem + env: COMPONENT=rails-hyperstack + - <<: *_deploy_gem + env: COMPONENT=hyperstack-config diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 20e35b6b5..99c76614b 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -34,7 +34,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'factory_bot_rails' spec.add_development_dependency 'hyper-spec', HyperModel::VERSION spec.add_development_dependency 'mini_racer' - spec.add_development_dependency 'pg' + spec.add_development_dependency 'mysql2' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'pry-stack_explorer' diff --git a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb index 7c54007d2..51a4c49fa 100644 --- a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb +++ b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb @@ -131,9 +131,7 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t, - json: {kind: :json}, - jsonb: {kind: :jsonb} + timestamp: t ) expect do TypeTest.columns_hash.collect do |attr, _info| @@ -141,7 +139,7 @@ class DefaultTest < ActiveRecord::Base end end.to_on_client eq([ 'Number', 'NilClass', 'Boolean', 'Date', 'Time', 'Number', 'Number', 'Number', - 'Number', 'String', 'String', 'Time', 'Time', 'NilClass', 'NilClass' + 'Number', 'String', 'String', 'Time', 'Time' ]) check_errors end @@ -171,7 +169,7 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t # see default tests below for json and jsonb + timestamp: t ) expect do t = TypeTest.find(1) @@ -199,9 +197,7 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t.time, - timestamp: t.time, - json: {kind: :json}, - jsonb: {kind: :jsonb} + timestamp: t.time ) expect do Hyperstack::Model.load do @@ -222,9 +218,7 @@ class DefaultTest < ActiveRecord::Base 'String', 'hello', 'String', 'goodby', 'Time', t.time_only.as_json, # date is indeterminate for active record time - 'Time', t.as_json, - 'Hash', {'kind' => 'json'}, - 'Hash', {'kind' => 'jsonb'} + 'Time', t.as_json ]) check_errors end @@ -308,14 +302,12 @@ class DefaultTest < ActiveRecord::Base [ t.string, t.date, t.datetime, t.integer_from_string, t.integer_from_int, t.float_from_string, t.float_from_float, - t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value, - t.json[:kind], t.jsonb[:kind] # the default for json and jsonb is nil so we will test dummy operations here + t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value ] end.to eq([ "I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json, 99, 98, 0.02, 0.01, - false, true, false, - 'json', 'jsonb' + false, true, false ]) check_errors end diff --git a/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb index 82ce7fbd4..f7433d6dc 100644 --- a/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb @@ -11,8 +11,6 @@ def self.build_tables t.boolean :boolean_from_falsy_string, default: "OFF" t.boolean :boolean_from_truthy_string, default: "something-else" t.boolean :boolean_from_falsy_value, default: false - t.json :json, default: {kind: :json} - t.jsonb :jsonb, default: {kind: :jsonb} end end end diff --git a/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb index 50fc55d53..3f8920a16 100644 --- a/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb @@ -13,8 +13,6 @@ def self.build_tables t.text(:text) t.time(:time) t.timestamp(:timestamp) - t.json(:json) - t.jsonb(:jsonb) end end end diff --git a/ruby/hyper-model/spec/test_app/config/database.yml b/ruby/hyper-model/spec/test_app/config/database.yml index ad8c9dd1c..92191c367 100644 --- a/ruby/hyper-model/spec/test_app/config/database.yml +++ b/ruby/hyper-model/spec/test_app/config/database.yml @@ -1,30 +1,8 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -# -# default: &default -# adapter: mysql2 -# encoding: utf8 -# username: root -# -# development: -# <<: *default -# database: hyper_mesh_development_db -# -# test: -# <<: *default -# database: hyper_mesh_test_db -# -# production: -# <<: *default -# database: hyper_mesh_production_db default: &default - adapter: postgresql - pool: 5 - timeout: 5000 + adapter: mysql2 + encoding: utf8 + username: root development: <<: *default From 27d06046ae693eae2dbad1b7c7f41e8396297c9c Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 24 Feb 2021 17:05:58 -0500 Subject: [PATCH 132/307] latest rails compatibility and put schema back for hyper-model --- ruby/hyper-model/spec/test_app/db/schema.rb | 101 ++++++++++++++++++ .../test_app/app/assets/config/manifest.js | 2 + 2 files changed, 103 insertions(+) create mode 100644 ruby/hyper-model/spec/test_app/db/schema.rb create mode 100644 ruby/hyper-spec/spec/test_app/app/assets/config/manifest.js diff --git a/ruby/hyper-model/spec/test_app/db/schema.rb b/ruby/hyper-model/spec/test_app/db/schema.rb new file mode 100644 index 000000000..03ed1ad18 --- /dev/null +++ b/ruby/hyper-model/spec/test_app/db/schema.rb @@ -0,0 +1,101 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `rails +# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2016_07_31_182106) do + + create_table "addresses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.string "street" + t.string "city" + t.string "state" + t.string "zip" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "child_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.string "child_attribute" + t.integer "test_model_id" + end + + create_table "comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.text "comment" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "todo_id" + t.integer "author_id" + t.integer "user_id" + t.integer "todo_item_id" + end + + create_table "hyperstack_connections", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.string "channel" + t.string "session" + t.datetime "created_at" + t.datetime "expires_at" + t.datetime "refresh_at" + end + + create_table "hyperstack_queued_messages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.text "data" + t.integer "connection_id" + end + + create_table "test_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.string "test_attribute" + t.boolean "completed" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "todo_items", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.string "title" + t.text "description" + t.boolean "complete" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "user_id" + t.integer "comment_id" + end + + create_table "todos", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.string "title" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "completed", default: false, null: false + t.integer "created_by_id" + t.integer "owner_id" + end + + create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.string "role" + t.integer "manager_id" + t.string "first_name" + t.string "last_name" + t.string "email" + t.datetime "created_at" + t.datetime "updated_at" + t.string "address_street" + t.string "address_city" + t.string "address_state" + t.string "address_zip" + t.integer "address_id" + t.string "address2_street" + t.string "address2_city" + t.string "address2_state" + t.string "address2_zip" + t.string "data_string" + t.integer "data_times" + t.integer "test_enum" + end + +end diff --git a/ruby/hyper-spec/spec/test_app/app/assets/config/manifest.js b/ruby/hyper-spec/spec/test_app/app/assets/config/manifest.js new file mode 100644 index 000000000..21a78805c --- /dev/null +++ b/ruby/hyper-spec/spec/test_app/app/assets/config/manifest.js @@ -0,0 +1,2 @@ +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css From 41fd864a32f9bf10951c344c54bce1061bbdb32d Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 24 Feb 2021 23:14:56 -0500 Subject: [PATCH 133/307] fixed primary_key against string instead of class --- ruby/hyper-model/lib/reactive_record/broadcast.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-model/lib/reactive_record/broadcast.rb b/ruby/hyper-model/lib/reactive_record/broadcast.rb index 699ed9f3e..bf3214818 100644 --- a/ruby/hyper-model/lib/reactive_record/broadcast.rb +++ b/ruby/hyper-model/lib/reactive_record/broadcast.rb @@ -148,7 +148,7 @@ def local(operation, record, data) @klass = record.class.name @record = data record.backing_record.destroyed = false - @record[@klass.primary_key] = record.id if record.id + @record[record.primary_key] = record.id if record.id record.backing_record.destroyed = @destroyed @backing_record = record.backing_record @previous_changes = record.changes From ce50af2ac4229acf2a168fe229d5d28f08379bcd Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 25 Feb 2021 08:50:59 -0500 Subject: [PATCH 134/307] following travis cs instructions --- .travis.yml | 195 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 119 insertions(+), 76 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29a91d3cc..6ede77e6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,80 +1,123 @@ -language: bash -cache: - bundler: true - directories: - - node_modules # NPM packages -_test_gem: &_test_gem - # env: - # global: - # - PGPORT=5433 - stage: test - services: - - postgresql - addons: - apt: - sources: - - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' - packages: - - chromium-chromedriver - - google-chrome-stable - - yarn - - redis-server - mariadb: '10.3' # mariadb works fine... - postgresql: "11.2" - before_install: - - sudo apt-get update - - sudo apt-get --yes remove postgresql\* - - sudo apt-get install -y postgresql-11 postgresql-client-11 - - | - sudo cat </etc/postgresql/11/main/pg_hba.conf - local all postgres trust - local all all trust - host all all 127.0.0.1/32 trust - host all all ::1/128 trust - EOF - # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo cat /etc/postgresql/11/main/pg_hba.conf - - echo installing $COMPONENT - - sudo rm -f /usr/local/bin/yarn - - nvm install 10 - - rvm install 2.6.3 # was 2.5.1 - - gem install bundler - - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - before_script: - - echo creating pg database hyper_mesh_test_db - - psql --version - - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres - - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres - - echo before_script $COMPONENT - - cd ruby/$COMPONENT - - bundle install --jobs=3 --retry=3 - - bundle exec rake spec:prepare - - google-chrome --version - - which google-chrome - - yarn install - script: - - echo running script $COMPONENT - - DRIVER=travis bundle exec rake $TASK +dist: xenial -_deploy_gem: &_deploy_gem - stage: release gems - before_script: - - cd ruby/$COMPONENT - script: - - echo deploying $COMPONENT - deploy: - - provider: rubygems - api_key: - secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" - on: - tags: true -jobs: - include: - - <<: *_test_gem - env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 +addons: + apt: + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + packages: + - postgresql-11 + - chromium-chromedriver + - google-chrome-stable + - yarn + - redis-server + postgresql: '11' + +before_install: + - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf + - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo service postgresql stop + - sudo service postgresql start 11 + - postgres --version + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + - echo 'install completed' + +before_script: + - psql -c 'create database hyper_mesh_test_db;' -U postgres + - cd ruby/hyper-model + - bundle install --jobs=3 --retry=3 + - bundle exec rake spec:prepare + - google-chrome --version + - which google-chrome + - yarn install +script: + - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_types_spec.rb:121 +# +# +# +# language: bash +# cache: +# bundler: true +# directories: +# - node_modules # NPM packages +# _test_gem: &_test_gem +# # env: +# # global: +# # - PGPORT=5433 +# stage: test +# services: +# - postgresql +# addons: +# apt: +# sources: +# - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' +# key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' +# - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' +# key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' +# packages: +# - chromium-chromedriver +# - google-chrome-stable +# - yarn +# - redis-server +# mariadb: '10.3' # mariadb works fine... +# postgresql: "11.2" +# before_install: +# - sudo apt-get update +# - sudo apt-get --yes remove postgresql\* +# - sudo apt-get install -y postgresql-11 postgresql-client-11 +# - | +# sudo cat </etc/postgresql/11/main/pg_hba.conf +# local all postgres trust +# local all all trust +# host all all 127.0.0.1/32 trust +# host all all ::1/128 trust +# EOF +# # - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf +# - sudo cat /etc/postgresql/11/main/pg_hba.conf +# - echo installing $COMPONENT +# - sudo rm -f /usr/local/bin/yarn +# - nvm install 10 +# - rvm install 2.6.3 # was 2.5.1 +# - gem install bundler +# - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver +# before_script: +# - echo creating pg database hyper_mesh_test_db +# - psql --version +# - psql -c 'CREATE DATABASE hyper_mesh_test_db;' -U postgres +# - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres +# - echo before_script $COMPONENT +# - cd ruby/$COMPONENT +# - bundle install --jobs=3 --retry=3 +# - bundle exec rake spec:prepare +# - google-chrome --version +# - which google-chrome +# - yarn install +# script: +# - echo running script $COMPONENT +# - DRIVER=travis bundle exec rake $TASK +# +# _deploy_gem: &_deploy_gem +# stage: release gems +# before_script: +# - cd ruby/$COMPONENT +# script: +# - echo deploying $COMPONENT +# deploy: +# - provider: rubygems +# api_key: +# secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" +# on: +# tags: true +# jobs: +# include: +# - <<: *_test_gem +# env: PGPORT=5433 COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 # language: bash # cache: From 62076e095a40ba1a8f2770629b37717702922923 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 25 Feb 2021 09:00:18 -0500 Subject: [PATCH 135/307] removed spec prepare for now --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ede77e6b..83045b73d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,6 @@ before_script: - psql -c 'create database hyper_mesh_test_db;' -U postgres - cd ruby/hyper-model - bundle install --jobs=3 --retry=3 - - bundle exec rake spec:prepare - google-chrome --version - which google-chrome - yarn install From 893a185716db5f7f6cc833d30632f3ef4ee34662 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 25 Feb 2021 09:04:48 -0500 Subject: [PATCH 136/307] typo in name of example spec --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 83045b73d..104c7edf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ before_script: - which google-chrome - yarn install script: - - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_types_spec.rb:121 + - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 # # # From b9a0a836066150b1a8c3aec6e3e44b149326c4e0 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 25 Feb 2021 09:17:05 -0500 Subject: [PATCH 137/307] now moving the actual spec to a job with install outside the job --- .travis.yml | 69 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 104c7edf6..00d0dc5f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,45 @@ +# This works: See build 717 https://travis-ci.com/github/hyperstack-org/hyperstack/builds/218241993 + +# dist: xenial +# +# addons: +# apt: +# sources: +# - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' +# key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' +# - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' +# key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' +# packages: +# - postgresql-11 +# - chromium-chromedriver +# - google-chrome-stable +# - yarn +# - redis-server +# postgresql: '11' +# +# before_install: +# - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf +# - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf +# - sudo service postgresql stop +# - sudo service postgresql start 11 +# - postgres --version +# - sudo rm -f /usr/local/bin/yarn +# - nvm install 10 +# - rvm install 2.6.3 # was 2.5.1 +# - gem install bundler +# - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver +# - echo 'install completed' +# +# before_script: +# - psql -c 'create database hyper_mesh_test_db;' -U postgres +# - cd ruby/hyper-model +# - bundle install --jobs=3 --retry=3 +# - google-chrome --version +# - which google-chrome +# - yarn install +# script: +# - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 +#--------------------------------------------------------------------------------------- dist: xenial addons: @@ -29,14 +71,25 @@ before_install: - echo 'install completed' before_script: - - psql -c 'create database hyper_mesh_test_db;' -U postgres - - cd ruby/hyper-model - - bundle install --jobs=3 --retry=3 - - google-chrome --version - - which google-chrome - - yarn install -script: - - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 + - psql -c 'create database hyper_mesh_test_db;' -U postgres + +_test_gem: &_test_gem + before_script: + - cd ruby/$COMPONENT + - bundle install --jobs=3 --retry=3 + - google-chrome --version + - which google-chrome + - yarn install + script: + - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 + +jobs: + include: + - <<: *_test_gem + env: COMPONENT=hyper-model + +# ------------------ + # # # From d92ec281dc45e395e8d27c33f1a9053fc8322b17 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 25 Feb 2021 09:26:56 -0500 Subject: [PATCH 138/307] now moving the pg install inside the job --- .travis.yml | 87 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 00d0dc5f9..43d800f07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,62 @@ # script: # - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 #--------------------------------------------------------------------------------------- +# +# This fails because within the job the db does not exist +# see job 718: https://travis-ci.com/github/hyperstack-org/hyperstack/builds/218243353 +# +# dist: xenial +# +# addons: +# apt: +# sources: +# - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' +# key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' +# - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' +# key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' +# packages: +# - postgresql-11 +# - chromium-chromedriver +# - google-chrome-stable +# - yarn +# - redis-server +# postgresql: '11' +# +# before_install: +# - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf +# - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf +# - sudo service postgresql stop +# - sudo service postgresql start 11 +# - postgres --version +# - sudo rm -f /usr/local/bin/yarn +# - nvm install 10 +# - rvm install 2.6.3 # was 2.5.1 +# - gem install bundler +# - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver +# - echo 'install completed' +# +# before_script: +# - psql -c 'create database hyper_mesh_test_db;' -U postgres +# +# _test_gem: &_test_gem +# before_script: +# - cd ruby/$COMPONENT +# - bundle install --jobs=3 --retry=3 +# - google-chrome --version +# - which google-chrome +# - yarn install +# script: +# - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 +# +# jobs: +# include: +# - <<: *_test_gem +# env: COMPONENT=hyper-model +#--------------------------------------------------------------------------------------- +# +# This fails because within the job the db does not exist +# see job 718: https://travis-ci.com/github/hyperstack-org/hyperstack/builds/218243353 +# dist: xenial addons: @@ -57,24 +113,22 @@ addons: - redis-server postgresql: '11' -before_install: - - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf - - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - - sudo service postgresql stop - - sudo service postgresql start 11 - - postgres --version - - sudo rm -f /usr/local/bin/yarn - - nvm install 10 - - rvm install 2.6.3 # was 2.5.1 - - gem install bundler - - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - - echo 'install completed' - -before_script: - - psql -c 'create database hyper_mesh_test_db;' -U postgres - _test_gem: &_test_gem + before_install: + - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf + - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo service postgresql stop + - sudo service postgresql start 11 + - postgres --version + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + - echo 'install completed' + before_script: + - psql -c 'create database hyper_mesh_test_db;' -U postgres - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - google-chrome --version @@ -87,7 +141,6 @@ jobs: include: - <<: *_test_gem env: COMPONENT=hyper-model - # ------------------ # From c8c3798666ad9b096d7874c4904fd08bcfe1e92b Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 27 Feb 2021 13:39:27 -0500 Subject: [PATCH 139/307] adding conditional --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 43d800f07..73a08fdd5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -115,6 +115,7 @@ addons: _test_gem: &_test_gem before_install: + - [ ! -z "${USE_PG}" ] && echo 'installing postgresql' - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - sudo service postgresql stop @@ -140,7 +141,7 @@ _test_gem: &_test_gem jobs: include: - <<: *_test_gem - env: COMPONENT=hyper-model + env: COMPONENT=hyper-model USE_PG=hyper_mesh_test_db # ------------------ # From 163f27fa8d7a1bd7c5bf33cff1502e6f14f2f073 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 27 Feb 2021 14:39:01 -0500 Subject: [PATCH 140/307] trying it this way --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 73a08fdd5..ea341495d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -113,9 +113,9 @@ addons: - redis-server postgresql: '11' -_test_gem: &_test_gem +_test_gem_pg: &_test_gem_pg before_install: - - [ ! -z "${USE_PG}" ] && echo 'installing postgresql' + - echo 'installing postgresql' - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf - sudo service postgresql stop @@ -129,7 +129,7 @@ _test_gem: &_test_gem - echo 'install completed' before_script: - - psql -c 'create database hyper_mesh_test_db;' -U postgres + - psql -c 'create database $DB;' -U postgres - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - google-chrome --version @@ -140,8 +140,8 @@ _test_gem: &_test_gem jobs: include: - - <<: *_test_gem - env: COMPONENT=hyper-model USE_PG=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model DB=hyper_mesh_test_db # ------------------ # From 6a6ecea8ed90658409aaa7afb20bed740812689c Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 27 Feb 2021 16:59:27 -0500 Subject: [PATCH 141/307] trying again --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ea341495d..47e20eb1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -129,7 +129,7 @@ _test_gem_pg: &_test_gem_pg - echo 'install completed' before_script: - - psql -c 'create database $DB;' -U postgres + - psql -c "create database ${DB};" -U postgres - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - google-chrome --version From 29e177fb8605be3007513244a720f7a2ad5dc442 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 27 Feb 2021 17:23:10 -0500 Subject: [PATCH 142/307] git git git --- .postgresql.travis.yml | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .postgresql.travis.yml diff --git a/.postgresql.travis.yml b/.postgresql.travis.yml new file mode 100644 index 000000000..fe8744287 --- /dev/null +++ b/.postgresql.travis.yml @@ -0,0 +1,46 @@ +dist: xenial + +addons: + apt: + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + packages: + - postgresql-11 + - chromium-chromedriver + - google-chrome-stable + - yarn + - redis-server + postgresql: '11' + +_test_gem_pg: &_test_gem_pg + before_install: + - echo 'installing postgresql' + - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf + - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf + - sudo service postgresql stop + - sudo service postgresql start 11 + - postgres --version + - sudo rm -f /usr/local/bin/yarn + - nvm install 10 + - rvm install 2.6.3 # was 2.5.1 + - gem install bundler + - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + - echo 'install completed' + + before_script: + - psql -c "create database ${DB};" -U postgres + - cd ruby/$COMPONENT + - bundle install --jobs=3 --retry=3 + - google-chrome --version + - which google-chrome + - yarn install + script: + - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 + +jobs: + include: + - <<: *_test_gem_pg + env: COMPONENT=hyper-model DB=hyper_mesh_test_db From 9007b95f1983e48027381a9f9edf0e7294aa1f16 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 27 Feb 2021 17:46:47 -0500 Subject: [PATCH 143/307] integrating postgre hyper-model specs --- ruby/hyper-model/hyper-model.gemspec | 2 +- .../batch1/column_types/column_type_spec.rb | 22 ++++++++++----- .../app/hyperstack/models/default_test.rb | 2 ++ .../app/hyperstack/models/type_test.rb | 2 ++ .../spec/test_app/config/database.yml | 28 +++++++++++++++++-- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 99c76614b..20e35b6b5 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -34,7 +34,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'factory_bot_rails' spec.add_development_dependency 'hyper-spec', HyperModel::VERSION spec.add_development_dependency 'mini_racer' - spec.add_development_dependency 'mysql2' + spec.add_development_dependency 'pg' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' spec.add_development_dependency 'pry-rescue' spec.add_development_dependency 'pry-stack_explorer' diff --git a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb index 51a4c49fa..7c54007d2 100644 --- a/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb +++ b/ruby/hyper-model/spec/batch1/column_types/column_type_spec.rb @@ -131,7 +131,9 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t + timestamp: t, + json: {kind: :json}, + jsonb: {kind: :jsonb} ) expect do TypeTest.columns_hash.collect do |attr, _info| @@ -139,7 +141,7 @@ class DefaultTest < ActiveRecord::Base end end.to_on_client eq([ 'Number', 'NilClass', 'Boolean', 'Date', 'Time', 'Number', 'Number', 'Number', - 'Number', 'String', 'String', 'Time', 'Time' + 'Number', 'String', 'String', 'Time', 'Time', 'NilClass', 'NilClass' ]) check_errors end @@ -169,7 +171,7 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t, - timestamp: t + timestamp: t # see default tests below for json and jsonb ) expect do t = TypeTest.find(1) @@ -197,7 +199,9 @@ class DefaultTest < ActiveRecord::Base string: "hello", text: "goodby", time: t.time, - timestamp: t.time + timestamp: t.time, + json: {kind: :json}, + jsonb: {kind: :jsonb} ) expect do Hyperstack::Model.load do @@ -218,7 +222,9 @@ class DefaultTest < ActiveRecord::Base 'String', 'hello', 'String', 'goodby', 'Time', t.time_only.as_json, # date is indeterminate for active record time - 'Time', t.as_json + 'Time', t.as_json, + 'Hash', {'kind' => 'json'}, + 'Hash', {'kind' => 'jsonb'} ]) check_errors end @@ -302,12 +308,14 @@ class DefaultTest < ActiveRecord::Base [ t.string, t.date, t.datetime, t.integer_from_string, t.integer_from_int, t.float_from_string, t.float_from_float, - t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value + t.boolean_from_falsy_string, t.boolean_from_truthy_string, t.boolean_from_falsy_value, + t.json[:kind], t.jsonb[:kind] # the default for json and jsonb is nil so we will test dummy operations here ] end.to eq([ "I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json, 99, 98, 0.02, 0.01, - false, true, false + false, true, false, + 'json', 'jsonb' ]) check_errors end diff --git a/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb index f7433d6dc..82ce7fbd4 100644 --- a/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/default_test.rb @@ -11,6 +11,8 @@ def self.build_tables t.boolean :boolean_from_falsy_string, default: "OFF" t.boolean :boolean_from_truthy_string, default: "something-else" t.boolean :boolean_from_falsy_value, default: false + t.json :json, default: {kind: :json} + t.jsonb :jsonb, default: {kind: :jsonb} end end end diff --git a/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb index 3f8920a16..50fc55d53 100644 --- a/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb +++ b/ruby/hyper-model/spec/test_app/app/hyperstack/models/type_test.rb @@ -13,6 +13,8 @@ def self.build_tables t.text(:text) t.time(:time) t.timestamp(:timestamp) + t.json(:json) + t.jsonb(:jsonb) end end end diff --git a/ruby/hyper-model/spec/test_app/config/database.yml b/ruby/hyper-model/spec/test_app/config/database.yml index 92191c367..ad8c9dd1c 100644 --- a/ruby/hyper-model/spec/test_app/config/database.yml +++ b/ruby/hyper-model/spec/test_app/config/database.yml @@ -1,8 +1,30 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +# default: &default +# adapter: mysql2 +# encoding: utf8 +# username: root +# +# development: +# <<: *default +# database: hyper_mesh_development_db +# +# test: +# <<: *default +# database: hyper_mesh_test_db +# +# production: +# <<: *default +# database: hyper_mesh_production_db default: &default - adapter: mysql2 - encoding: utf8 - username: root + adapter: postgresql + pool: 5 + timeout: 5000 development: <<: *default From 11234e8cd727ca7dc01a9a15432a81dedcd048f7 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 27 Feb 2021 17:51:51 -0500 Subject: [PATCH 144/307] trying one more time --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index d27d40cfc..ed6dfb411 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ dist: xenial -language: bash -cache: - bundler: true - directories: - - node_modules # NPM packages +# language: bash +# cache: +# bundler: true +# directories: +# - node_modules # NPM packages addons: apt: From 679077b40629e40bc4cbfa6a5f65846dec97d77d Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 27 Feb 2021 18:05:03 -0500 Subject: [PATCH 145/307] lets see if this works --- .travis.yml | 212 ++++++++++++++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/.travis.yml b/.travis.yml index ed6dfb411..c30e2343a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,8 @@ _test_gem_pg: &_test_gem_pg - which google-chrome - yarn install script: - - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 + - echo running script $COMPONENT + - DRIVER=travis bundle exec rake $TASK _test_gem: &_test_gem stage: test @@ -80,112 +81,111 @@ _test_gem: &_test_gem - which google-chrome - yarn install script: - - DRIVER=travis bundle exec rspec spec/batch1/column_types/column_type_spec.rb:121 + - echo running script $COMPONENT + - DRIVER=travis bundle exec rake $TASK jobs: include: + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - <<: *_test_gem_pg - env: COMPONENT=hyper-model DB=hyper_mesh_test_db - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - # - <<: *_deploy_gem - # env: COMPONENT=hyper-i18n - # - <<: *_deploy_gem - # env: COMPONENT=hyper-trace - # - <<: *_deploy_gem - # env: COMPONENT=hyper-state - # - <<: *_deploy_gem - # env: COMPONENT=hyper-component - # - <<: *_deploy_gem - # env: COMPONENT=hyper-model - # - <<: *_deploy_gem - # env: COMPONENT=hyper-operation - # - <<: *_deploy_gem - # env: COMPONENT=hyper-router - # - <<: *_deploy_gem - # env: COMPONENT=hyper-spec - # - <<: *_deploy_gem - # env: COMPONENT=hyper-store - # - <<: *_deploy_gem - # env: COMPONENT=rails-hyperstack - # - <<: *_deploy_gem - # env: COMPONENT=hyperstack-config + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 DB=hyper_mesh_test_db + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + + - <<: *_deploy_gem + env: COMPONENT=hyper-i18n + - <<: *_deploy_gem + env: COMPONENT=hyper-trace + - <<: *_deploy_gem + env: COMPONENT=hyper-state + - <<: *_deploy_gem + env: COMPONENT=hyper-component + - <<: *_deploy_gem + env: COMPONENT=hyper-model + - <<: *_deploy_gem + env: COMPONENT=hyper-operation + - <<: *_deploy_gem + env: COMPONENT=hyper-router + - <<: *_deploy_gem + env: COMPONENT=hyper-spec + - <<: *_deploy_gem + env: COMPONENT=hyper-store + - <<: *_deploy_gem + env: COMPONENT=rails-hyperstack + - <<: *_deploy_gem + env: COMPONENT=hyperstack-config From 82d563a7c2831ab6c52da4b512739a6e4afc4162 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 15:37:27 -0500 Subject: [PATCH 146/307] adjustments to run postgresql --- ruby/hyper-model/Rakefile | 2 +- .../batch2/non_ar_aggregations_tbdspec.rb | 1 - .../spec/batch5/load_from_json_xspec.rb | 1 - .../config/initializers/hyperstack.rb | 1 + .../20210228200459_add_connection_tables.rb | 16 ++++ ruby/hyper-model/spec/test_app/db/schema.rb | 89 +++++++++++++++---- 6 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 ruby/hyper-model/spec/test_app/db/migrate/20210228200459_add_connection_tables.rb diff --git a/ruby/hyper-model/Rakefile b/ruby/hyper-model/Rakefile index 1f5cc17e0..eeb360644 100644 --- a/ruby/hyper-model/Rakefile +++ b/ruby/hyper-model/Rakefile @@ -32,7 +32,7 @@ end namespace :spec do task :prepare do - sh %(cd spec/test_app; bundle exec rails db:setup) + sh %(cd spec/test_app; rm db/schema.rb; RAILS_ENV=test bundle exec rails db:setup; RAILS_ENV=test bundle exec rails db:migrate) end (1..7).each do |batch| RSpec::Core::RakeTask.new(:"batch#{batch}") do |t| diff --git a/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb b/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb index 7b56b1474..ed7ba687c 100644 --- a/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb +++ b/ruby/hyper-model/spec/batch2/non_ar_aggregations_tbdspec.rb @@ -41,7 +41,6 @@ User.find_by_first_name("Data").save end expect(User.find_by_first_name("Data").data.big_string).to eq("hellohellohello") - binding.pry end it "read it" do diff --git a/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb b/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb index dfca078ce..4066adb2c 100644 --- a/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb +++ b/ruby/hyper-model/spec/batch5/load_from_json_xspec.rb @@ -26,7 +26,6 @@ end it '*all key' do - binding.pry evaluate_ruby do User.all.count end diff --git a/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb b/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb index 1d1a370cd..7eca39707 100644 --- a/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb +++ b/ruby/hyper-model/spec/test_app/config/initializers/hyperstack.rb @@ -2,3 +2,4 @@ Hyperstack.cancel_import 'hyperstack/autoloader' Hyperstack.cancel_import 'hyperstack/autoloader_starter' Hyperstack.cancel_import 'config/initializers/inflections.rb' +def (Hyperstack::Connection).build_tables?; false; end diff --git a/ruby/hyper-model/spec/test_app/db/migrate/20210228200459_add_connection_tables.rb b/ruby/hyper-model/spec/test_app/db/migrate/20210228200459_add_connection_tables.rb new file mode 100644 index 000000000..e2faf1096 --- /dev/null +++ b/ruby/hyper-model/spec/test_app/db/migrate/20210228200459_add_connection_tables.rb @@ -0,0 +1,16 @@ +class AddConnectionTables < ActiveRecord::Migration[5.2] + def change + create_table "hyperstack_connections", force: :cascade do |t| + t.string "channel" + t.string "session" + t.datetime "created_at" + t.datetime "expires_at" + t.datetime "refresh_at" + end + + create_table "hyperstack_queued_messages", force: :cascade do |t| + t.text "data" + t.integer "connection_id" + end + end +end diff --git a/ruby/hyper-model/spec/test_app/db/schema.rb b/ruby/hyper-model/spec/test_app/db/schema.rb index 03ed1ad18..23819ff59 100644 --- a/ruby/hyper-model/spec/test_app/db/schema.rb +++ b/ruby/hyper-model/spec/test_app/db/schema.rb @@ -10,9 +10,12 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2016_07_31_182106) do +ActiveRecord::Schema.define(version: 2021_02_28_200459) do - create_table "addresses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "addresses", force: :cascade do |t| t.string "street" t.string "city" t.string "state" @@ -21,22 +24,44 @@ t.datetime "updated_at" end - create_table "child_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "bones", force: :cascade do |t| + t.integer "dog_id" + end + + create_table "child_models", force: :cascade do |t| t.string "child_attribute" - t.integer "test_model_id" + t.bigint "test_model_id" + t.index ["test_model_id"], name: "index_child_models_on_test_model_id" end - create_table "comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "comments", force: :cascade do |t| t.text "comment" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "todo_id" - t.integer "author_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "todo_id" + t.bigint "author_id" t.integer "user_id" t.integer "todo_item_id" + t.index ["author_id"], name: "index_comments_on_author_id" + t.index ["todo_id"], name: "index_comments_on_todo_id" + end + + create_table "default_tests", force: :cascade do |t| + t.string "string", default: "I'm a string!" + t.date "date", default: "2021-02-27" + t.datetime "datetime", default: "2021-02-27 22:45:41" + t.integer "integer_from_string", default: 99 + t.integer "integer_from_int", default: 98 + t.float "float_from_string", default: 0.02 + t.float "float_from_float", default: 0.01 + t.boolean "boolean_from_falsy_string", default: false + t.boolean "boolean_from_truthy_string", default: true + t.boolean "boolean_from_falsy_value", default: false + t.json "json", default: {"kind"=>"json"} + t.jsonb "jsonb", default: {"kind"=>"jsonb"} end - create_table "hyperstack_connections", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "hyperstack_connections", force: :cascade do |t| t.string "channel" t.string "session" t.datetime "created_at" @@ -44,19 +69,27 @@ t.datetime "refresh_at" end - create_table "hyperstack_queued_messages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "hyperstack_queued_messages", force: :cascade do |t| t.text "data" t.integer "connection_id" end - create_table "test_models", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "pets", force: :cascade do |t| + t.integer "owner_id" + end + + create_table "scratching_posts", force: :cascade do |t| + t.integer "cat_id" + end + + create_table "test_models", force: :cascade do |t| t.string "test_attribute" t.boolean "completed" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "todo_items", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "todo_items", force: :cascade do |t| t.string "title" t.text "description" t.boolean "complete" @@ -66,19 +99,38 @@ t.integer "comment_id" end - create_table "todos", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "todos", force: :cascade do |t| t.string "title" t.text "description" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "completed", default: false, null: false - t.integer "created_by_id" - t.integer "owner_id" + t.bigint "created_by_id" + t.bigint "owner_id" + t.index ["created_by_id"], name: "index_todos_on_created_by_id" + t.index ["owner_id"], name: "index_todos_on_owner_id" + end + + create_table "type_tests", force: :cascade do |t| + t.binary "binary" + t.boolean "boolean" + t.date "date" + t.datetime "datetime" + t.decimal "decimal", precision: 5, scale: 2 + t.float "float" + t.integer "integer" + t.bigint "bigint" + t.string "string" + t.text "text" + t.time "time" + t.datetime "timestamp" + t.json "json" + t.jsonb "jsonb" end - create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "users", force: :cascade do |t| t.string "role" - t.integer "manager_id" + t.bigint "manager_id" t.string "first_name" t.string "last_name" t.string "email" @@ -96,6 +148,7 @@ t.string "data_string" t.integer "data_times" t.integer "test_enum" + t.index ["manager_id"], name: "index_users_on_manager_id" end end From 376853b5a82bbe9364e87581dbe70ea4bc77b10e Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 16:02:21 -0500 Subject: [PATCH 147/307] ignore certain errors with PG --- .../transport/connection_adapter/active_record.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb index fa7938199..f503b0c24 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/connection_adapter/active_record.rb @@ -38,6 +38,8 @@ def active end Connection.all.pluck(:channel).uniq + rescue ::ActiveRecord::StatementInvalid + [] end def open(channel, session = nil, root_path = nil) From 18d194b6aaac0945a2492e066f648e05d1ba609c Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 16:57:03 -0500 Subject: [PATCH 148/307] put the spec:prepare back in --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c30e2343a..ad3ed7b48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,9 +37,10 @@ _test_gem_pg: &_test_gem_pg - echo 'install completed' before_script: - - psql -c "create database ${DB};" -U postgres + - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 + - bundle exec rake spec:prepare - google-chrome --version - which google-chrome - yarn install @@ -77,6 +78,7 @@ _test_gem: &_test_gem - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 + - bundle exec rake spec:prepare - google-chrome --version - which google-chrome - yarn install From 4473e8d1c12dfc9870a95a547215fe773602d293 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 17:11:13 -0500 Subject: [PATCH 149/307] more fixups --- .../test_app/db/migrate/20160731182106_create_test_models.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb b/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb index 86dec7925..f4d8c58a3 100644 --- a/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb +++ b/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb @@ -49,7 +49,6 @@ def change t.references :author t.integer "user_id" t.integer "todo_item_id" - t.datetime "created_at" t.datetime "updated_at" end From befc563e010a1957eb46f6df52573ba7c1323fa2 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 17:21:46 -0500 Subject: [PATCH 150/307] one more time fixing these migrations --- .../test_app/db/migrate/20160731182106_create_test_models.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb b/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb index f4d8c58a3..03022f4ba 100644 --- a/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb +++ b/ruby/hyper-model/spec/test_app/db/migrate/20160731182106_create_test_models.rb @@ -49,7 +49,6 @@ def change t.references :author t.integer "user_id" t.integer "todo_item_id" - t.datetime "updated_at" end create_table "addresses" do |t| From 2d348d8fb48fbc6b21db3d3750c35288122b5780 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 18:23:03 -0500 Subject: [PATCH 151/307] attempt to get operation spec working --- .travis.yml | 204 +++++++++--------- .../spec/batch7/poly_assoc_spec.rb | 13 +- 2 files changed, 106 insertions(+), 111 deletions(-) diff --git a/.travis.yml b/.travis.yml index ad3ed7b48..ab1acaefc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,6 +63,8 @@ _test_gem: &_test_gem - yarn - redis-server mariadb: '10.3' + services: + - redis-server before_install: - echo installing $COMPONENT # yarn is in /usr/local/bin/yarn version 1.3.2 and is not a package @@ -88,106 +90,106 @@ _test_gem: &_test_gem jobs: include: - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - - <<: *_test_gem_pg - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 DB=hyper_mesh_test_db - - <<: *_test_gem_pg - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 DB=hyper_mesh_test_db - - <<: *_test_gem_pg - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 DB=hyper_mesh_test_db - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - - <<: *_test_gem - env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - - <<: *_test_gem - env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - - <<: *_deploy_gem - env: COMPONENT=hyper-i18n - - <<: *_deploy_gem - env: COMPONENT=hyper-trace - - <<: *_deploy_gem - env: COMPONENT=hyper-state - - <<: *_deploy_gem - env: COMPONENT=hyper-component - - <<: *_deploy_gem - env: COMPONENT=hyper-model - - <<: *_deploy_gem - env: COMPONENT=hyper-operation - - <<: *_deploy_gem - env: COMPONENT=hyper-router - - <<: *_deploy_gem - env: COMPONENT=hyper-spec - - <<: *_deploy_gem - env: COMPONENT=hyper-store - - <<: *_deploy_gem - env: COMPONENT=rails-hyperstack - - <<: *_deploy_gem - env: COMPONENT=hyperstack-config + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 DB=hyper_mesh_test_db + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 DB=hyper_mesh_test_db + # - <<: *_test_gem_pg + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 DB=hyper_mesh_test_db + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + # + # - <<: *_test_gem + # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + # - <<: *_test_gem + # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + # - <<: *_test_gem + # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + # + # - <<: *_deploy_gem + # env: COMPONENT=hyper-i18n + # - <<: *_deploy_gem + # env: COMPONENT=hyper-trace + # - <<: *_deploy_gem + # env: COMPONENT=hyper-state + # - <<: *_deploy_gem + # env: COMPONENT=hyper-component + # - <<: *_deploy_gem + # env: COMPONENT=hyper-model + # - <<: *_deploy_gem + # env: COMPONENT=hyper-operation + # - <<: *_deploy_gem + # env: COMPONENT=hyper-router + # - <<: *_deploy_gem + # env: COMPONENT=hyper-spec + # - <<: *_deploy_gem + # env: COMPONENT=hyper-store + # - <<: *_deploy_gem + # env: COMPONENT=rails-hyperstack + # - <<: *_deploy_gem + # env: COMPONENT=hyperstack-config diff --git a/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb b/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb index 0e416ac77..92fa07491 100644 --- a/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb +++ b/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb @@ -240,32 +240,25 @@ def compare_to_server(model, expression, expected_result, load=true) end it 'changing belongs to relationship on client' do - # not working yet compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture11', 'picture12'] compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22'] - evaluate_promise do + on_client do p = Picture.find(1) p.imageable = Product.find(1) p.save end - # wait_for_ajax # so pusher can initialize compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture12'], false - compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture11', 'picture21', 'picture22'], false + compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22', 'picture11'], false end it 'changing belongs to relationship on server' do - # compare_to_server @picture11, 'imageable.name', 'imageable1' # here for debug assist - # compare_to_server @picture11, 'imageable.ss', '123' # here for debug assist - - # just debugging here... when id doesn't change we don't realize that data is changing compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture11', 'picture12'] compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22'] p = Picture.find_by_name('picture11') p.imageable = @imageable2 p.save - # wait_for_ajax # so pusher can initialize compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture12'] - compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture11', 'picture21', 'picture22'] + compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22', 'picture11'] end From 179a9a15fa51881e96dce08636e1db862c3f1034 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 18:38:24 -0500 Subject: [PATCH 152/307] hopefully final change to .travis.yml --- .travis.yml | 202 ++++++++++++++++++++++++++-------------------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab1acaefc..5305d767c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -90,106 +90,106 @@ _test_gem: &_test_gem jobs: include: - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 - # - <<: *_test_gem_pg - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 DB=hyper_mesh_test_db - # - <<: *_test_gem_pg - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 DB=hyper_mesh_test_db - # - <<: *_test_gem_pg - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 DB=hyper_mesh_test_db - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - # - # - <<: *_test_gem - # env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - # - <<: *_test_gem - # env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 - # - <<: *_test_gem - # env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - # - # - <<: *_deploy_gem - # env: COMPONENT=hyper-i18n - # - <<: *_deploy_gem - # env: COMPONENT=hyper-trace - # - <<: *_deploy_gem - # env: COMPONENT=hyper-state - # - <<: *_deploy_gem - # env: COMPONENT=hyper-component - # - <<: *_deploy_gem - # env: COMPONENT=hyper-model - # - <<: *_deploy_gem - # env: COMPONENT=hyper-operation - # - <<: *_deploy_gem - # env: COMPONENT=hyper-router - # - <<: *_deploy_gem - # env: COMPONENT=hyper-spec - # - <<: *_deploy_gem - # env: COMPONENT=hyper-store - # - <<: *_deploy_gem - # env: COMPONENT=rails-hyperstack - # - <<: *_deploy_gem - # env: COMPONENT=hyperstack-config + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3 DB=hyper_mesh_test_db + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' + + - <<: *_test_gem + env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-component RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-router RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 + - <<: *_test_gem + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + - <<: *_test_gem + env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' + + - <<: *_deploy_gem + env: COMPONENT=hyper-i18n + - <<: *_deploy_gem + env: COMPONENT=hyper-trace + - <<: *_deploy_gem + env: COMPONENT=hyper-state + - <<: *_deploy_gem + env: COMPONENT=hyper-component + - <<: *_deploy_gem + env: COMPONENT=hyper-model + - <<: *_deploy_gem + env: COMPONENT=hyper-operation + - <<: *_deploy_gem + env: COMPONENT=hyper-router + - <<: *_deploy_gem + env: COMPONENT=hyper-spec + - <<: *_deploy_gem + env: COMPONENT=hyper-store + - <<: *_deploy_gem + env: COMPONENT=rails-hyperstack + - <<: *_deploy_gem + env: COMPONENT=hyperstack-config From 2bc94bef2b358b857d8e0fd7a6d586d2356138ca Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 19:40:31 -0500 Subject: [PATCH 153/307] run all hyper-model specs with pg, and fied regression in spec --- .travis.yml | 24 +++++++++---------- .../spec/batch7/poly_assoc_spec.rb | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5305d767c..3a37e5803 100644 --- a/.travis.yml +++ b/.travis.yml @@ -135,12 +135,12 @@ jobs: env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - <<: *_test_gem env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part1 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part2 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' TASK=part3 DB=hyper_mesh_test_db - <<: *_test_gem env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 OPAL_VERSION='~>0.11' @@ -162,12 +162,12 @@ jobs: env: COMPONENT=hyper-store RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - <<: *_test_gem env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 - - <<: *_test_gem - env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part1 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part2 DB=hyper_mesh_test_db + - <<: *_test_gem_pg + env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' TASK=part3 DB=hyper_mesh_test_db - <<: *_test_gem env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1 RAILS_VERSION='~>5.0' diff --git a/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb b/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb index 92fa07491..22082cfc8 100644 --- a/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb +++ b/ruby/hyper-model/spec/batch7/poly_assoc_spec.rb @@ -242,7 +242,7 @@ def compare_to_server(model, expression, expected_result, load=true) it 'changing belongs to relationship on client' do compare_to_server @imageable1, 'pictures.collect(&:name)', ['picture11', 'picture12'] compare_to_server @imageable2, 'pictures.collect(&:name)', ['picture21', 'picture22'] - on_client do + evaluate_ruby do p = Picture.find(1) p.imageable = Product.find(1) p.save From a3eb1d6a43012eee82154aaca7ff1f7f9198e377 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 28 Feb 2021 20:46:18 -0500 Subject: [PATCH 154/307] closes #365 --- .travis.yml | 13 ++++++++ .../active_record/instance_methods.rb | 8 +++++ .../spec/batch3/instance_methods_spec.rb | 33 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 ruby/hyper-model/spec/batch3/instance_methods_spec.rb diff --git a/.travis.yml b/.travis.yml index 3a37e5803..6c0366e60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,6 +88,19 @@ _test_gem: &_test_gem - echo running script $COMPONENT - DRIVER=travis bundle exec rake $TASK +_deploy_gem: &_deploy_gem + stage: release gems + before_script: + - cd ruby/$COMPONENT + script: + - echo deploying $COMPONENT + deploy: + - provider: rubygems + api_key: + secure: "ORJMyp20YFCkvujBfxoDPwEZy8R8YJaKwRhHZUDTPZPiS84mJA7Mqd0JjvRlF0mlH/WzspruM7hZV0CuMU8F/0raRhSUU9RBh5veZ/4ij9kboCYnfuqBVt6qPRtaf8DgKe7CWGioUrTISJCVKLnygY6gZd2aFXCEbqZMrkUvC7y43ymOoFoeyCLsXC0j5uJxdHgNfbaIUetIl2DQJUbC2Rgq1Iaxvi72Ae97TR2xRCu+ko8DopRpQCug6U81IhzXftizGfKwzecqVFjuMn3XEf+UDlU6xbvwWWkcwjYNAbP2Kk+mWwUMx36s+1Pyx8MOveYLTwnQJ6gHocZHzh7WJOD548JNU3F5oXIlUB4EzD20bCSIeRKOdxTuKrNk7W3a5qGERuQi4rkIlkKaFIBP55IkliUxvYxqr0WujsjO2reRcNhNcLVGCOaX6LZbWFR5bf0WiEOL4vOxPNw66sI2JVHoMmQeAYtL2ghxikdSPXKRc+inT3QiRBsh+ns8YrAP7sV4lX6r/qyWUtPh6kY8xIeTP4VzMviyf20m5u++omao/FSEtVnU3cro5KjrZLg3ILg4NpNG+xoRqPS/Hmxry5ZPrggqNrxoqWuO7pLd/NnV/AnLiT8rd2P0PTriP9uRIM8+fFfyOeGwbplOLrbWUPnCdQVWp6dYOrNgE2yDJ/I=" + on: + tags: true + jobs: include: - <<: *_test_gem diff --git a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb index 453ca8124..0cddc3367 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb @@ -162,6 +162,14 @@ def itself # end end + def increment!(attr) + load(attr).then { |current_value| update(attr => current_value + 1) } + end + + def decrement!(attr) + load(attr).then { |current_value| update(attr => current_value - 1) } + end + def load(*attributes, &block) first_time = true ReactiveRecord.load do diff --git a/ruby/hyper-model/spec/batch3/instance_methods_spec.rb b/ruby/hyper-model/spec/batch3/instance_methods_spec.rb new file mode 100644 index 000000000..72f1de57d --- /dev/null +++ b/ruby/hyper-model/spec/batch3/instance_methods_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'test_components' + +describe "Misc Instance Methods", :no_reset, js: true do + + before(:each) do + # spec_helper resets the policy system after each test so we have to setup + # before each test + stub_const 'TestApplication', Class.new + stub_const 'TestApplicationPolicy', Class.new + TestApplicationPolicy.class_eval do + always_allow_connection + regulate_all_broadcasts { |policy| policy.send_all } + allow_change(to: :all) { true } + end + end + + it "increment!" do + user = FactoryBot.create(:user, first_name: 'zero', data_times: 0) + user_id = user.id + evaluate_ruby { User.find(user_id).increment!(:data_times) } + expect(user.reload.data_times).to eq(1) + expect { User.find(user_id).data_times }.to_on_client eq(1) + end + + it "decrement!" do + user = FactoryBot.create(:user, first_name: 'one', data_times: 1) + user_id = user.id + evaluate_ruby { User.find(user_id).decrement!(:data_times) } + expect(user.reload.data_times).to eq(0) + expect { User.find(user_id).data_times }.to_on_client eq(0) + end +end From 3e86d39a98137e90cfa34b1cef5dc3c11bfb23fd Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 1 Mar 2021 11:06:01 -0500 Subject: [PATCH 155/307] hyper-spec no_reset was not clearing after leaving spec block - fixed --- ruby/hyper-spec/lib/hyper-spec.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index c64643980..5b802ce2e 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -53,8 +53,8 @@ class << self end end - def self.reset_between_examples=(value) - RSpec.configuration.reset_between_examples = value + def self.reset_between_examples + @reset_between_examples ||= [] end def self.reset_between_examples? @@ -106,13 +106,16 @@ def reset_sessions! RSpec.configure do |config| config.add_setting :reset_between_examples, default: true config.before(:all, no_reset: true) do - HyperSpec.reset_between_examples = false + HyperSpec.reset_between_examples << RSpec.configuration.reset_between_examples + RSpec.configuration.reset_between_examples = false end config.before(:all, no_reset: false) do - HyperSpec.reset_between_examples = true + HyperSpec.reset_between_examples << RSpec.configuration.reset_between_examples + RSpec.configuration.reset_between_examples = true end config.after(:all) do HyperSpec.reset_sessions! unless HyperSpec.reset_between_examples? + RSpec.configuration.reset_between_examples = HyperSpec.reset_between_examples.pop end config.before(:each) do |example| insure_page_loaded(true) if example.metadata[:js] && !HyperSpec.reset_between_examples? From 36516765f840eade6a8862682dd5a12774b259cd Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 15:08:24 -0500 Subject: [PATCH 156/307] closes #364 and gets hypertrace working with opal 1.x --- .travis.yml | 15 +- .../refs_element_method_spec.rb | 1 - ruby/hyper-model/Gemfile | 8 +- ruby/hyper-model/hyper-model.gemspec | 1 + ruby/hyper-model/lib/active_record_base.rb | 51 +++++-- .../active_record/associations.rb | 6 +- .../lib/reactive_record/active_record/base.rb | 2 +- .../active_record/class_methods.rb | 8 ++ .../active_record/instance_methods.rb | 4 +- .../active_record/reactive_record/base.rb | 1 + .../reactive_record/collection.rb | 27 +++- .../reactive_record/dummy_value.rb | 2 +- .../reactive_record/isomorphic_base.rb | 26 ++-- .../reactive_record/operations.rb | 2 +- .../active_record/reactive_record/setters.rb | 5 +- .../lib/reactive_record/broadcast.rb | 9 +- .../lib/reactive_record/server_data_cache.rb | 2 +- .../batch3/has_and_belongs_to_many_spec.rb | 112 +++++++++++++++ ruby/hyper-model/spec/spec_helper.rb | 2 +- .../spec/test_app/config/application.rb | 2 +- ruby/hyper-model/spec/test_components.rb | 2 +- .../transport/client_drivers.rb | 2 +- .../lib/hyperstack/internal/state/variable.rb | 4 +- .../lib/hyper_trace/hyper_trace.rb | 130 +++++++++++------- 24 files changed, 318 insertions(+), 106 deletions(-) create mode 100644 ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb diff --git a/.travis.yml b/.travis.yml index 6c0366e60..ba19b4098 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,6 @@ dist: xenial -# language: bash -# cache: -# bundler: true -# directories: -# - node_modules # NPM packages +language: bash addons: apt: @@ -22,6 +18,11 @@ addons: postgresql: '11' _test_gem_pg: &_test_gem_pg + stage: test + cache: + bundler: true + directories: + - node_modules # NPM packages before_install: - echo 'installing postgresql' - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf @@ -50,6 +51,10 @@ _test_gem_pg: &_test_gem_pg _test_gem: &_test_gem stage: test + cache: + bundler: true + directories: + - node_modules # NPM packages addons: apt: sources: diff --git a/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb b/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb index ac0e9530e..5f2978d3c 100644 --- a/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb +++ b/ruby/hyper-component/spec/client_features/refs_element_method_spec.rb @@ -76,7 +76,6 @@ class AnotherComponent < HyperComponent render(DIV) { "another component named #{@Others[:foo]}"} end class TestComponent < HyperComponent - after_mount { debugger unless @ref_1.ref == @ref_2.ref; nil } render do @ref_1 = AnotherComponent().as_node @ref_2 = @ref_1.render(foo: :bar) diff --git a/ruby/hyper-model/Gemfile b/ruby/hyper-model/Gemfile index a5a0cd35b..5eb536166 100644 --- a/ruby/hyper-model/Gemfile +++ b/ruby/hyper-model/Gemfile @@ -1,11 +1,11 @@ source 'https://rubygems.org' -#gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master" -# hyper-model is still using an ancient inlined version of hyper-spec -#gem 'hyper-spec', path: '../hyper-spec' gem 'hyper-state', path: '../hyper-state' gem 'hyper-component', path: '../hyper-component' gem 'hyper-operation', path: '../hyper-operation' gem 'hyper-spec', path: '../hyper-spec' +gem 'hyper-trace', path: '../hyper-trace' gem 'hyperstack-config', path: '../hyperstack-config' - +unless ENV['OPAL_VERSION']&.match("0.11") + gem 'opal-browser', git: 'https://github.com/opal/opal-browser' +end gemspec diff --git a/ruby/hyper-model/hyper-model.gemspec b/ruby/hyper-model/hyper-model.gemspec index 20e35b6b5..4c53ce7b5 100644 --- a/ruby/hyper-model/hyper-model.gemspec +++ b/ruby/hyper-model/hyper-model.gemspec @@ -33,6 +33,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'database_cleaner' spec.add_development_dependency 'factory_bot_rails' spec.add_development_dependency 'hyper-spec', HyperModel::VERSION + spec.add_development_dependency 'hyper-trace', HyperModel::VERSION spec.add_development_dependency 'mini_racer' spec.add_development_dependency 'pg' spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0' diff --git a/ruby/hyper-model/lib/active_record_base.rb b/ruby/hyper-model/lib/active_record_base.rb index d98716c99..373d842cd 100644 --- a/ruby/hyper-model/lib/active_record_base.rb +++ b/ruby/hyper-model/lib/active_record_base.rb @@ -5,17 +5,46 @@ module ActiveRecord # processes these arguments, and the will always leave the true server side scoping # proc in the `:server` opts. This method is common to client and server. class Base - def self._synchromesh_scope_args_check(args) - opts = if args.count == 2 && args[1].is_a?(Hash) - args[1].merge(server: args[0]) - elsif args[0].is_a? Hash - args[0] - else - { server: args[0] } - end - return opts if opts && opts[:server].respond_to?(:call) - raise 'must provide either a proc as the first arg or by the '\ - '`:server` option to scope and default_scope methods' + class << self + def _synchromesh_scope_args_check(args) + opts = if args.count == 2 && args[1].is_a?(Hash) + args[1].merge(server: args[0]) + elsif args[0].is_a? Hash + args[0] + else + { server: args[0] } + end + return opts if opts && opts[:server].respond_to?(:call) + raise 'must provide either a proc as the first arg or by the '\ + '`:server` option to scope and default_scope methods' + end + + alias pre_hyperstack_has_and_belongs_to_many has_and_belongs_to_many unless RUBY_ENGINE == 'opal' + + def has_and_belongs_to_many(other, opts = {}, &block) + join_table_name = [other.to_s, table_name].sort.join('_') + join_model_name = "HyperstackInternalHabtm#{join_table_name.singularize.camelize}" + join_model = + if Object.const_defined? join_model_name + Object.const_get(join_model_name) + else + Object.const_set(join_model_name, Class.new(ActiveRecord::Base)) + end + + join_model.class_eval { belongs_to other.to_s.singularize.to_sym } + + has_many join_model_name.underscore.pluralize.to_sym + + if RUBY_ENGINE == 'opal' + Object.const_set("HABTM_#{other.to_s.camelize}", join_model) + join_model.inheritance_column = nil + has_many other, through: join_model_name.underscore.pluralize.to_sym + else + join_model.table_name = join_table_name + join_model.belongs_to other + pre_hyperstack_has_and_belongs_to_many(other, opts, &block) + end + end end end if RUBY_ENGINE != 'opal' diff --git a/ruby/hyper-model/lib/reactive_record/active_record/associations.rb b/ruby/hyper-model/lib/reactive_record/active_record/associations.rb index 35a32a722..1ec9a664f 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/associations.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/associations.rb @@ -32,7 +32,7 @@ def self.reflection_finder(&block) module Associations class AssociationReflection - + attr_reader :klass_name attr_reader :association_foreign_key attr_reader :attribute attr_reader :macro @@ -79,6 +79,10 @@ def singular? @macro != :has_many end + def habtm? + through_association&.klass_name =~ /^HyperstackInternalHabtm/ + end + def through_association return unless @options[:through] @through_association ||= @owner_class.reflect_on_all_associations.detect do |association| diff --git a/ruby/hyper-model/lib/reactive_record/active_record/base.rb b/ruby/hyper-model/lib/reactive_record/active_record/base.rb index ae09951ff..b6b5e5eec 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/base.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/base.rb @@ -16,7 +16,7 @@ class Base ) def self.__hyperstack_internal_scoped_find_by(attrs) - collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs) + collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs).observed if !collection.collection collection._find_by_initializer(self, attrs) else diff --git a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb index dd467d728..4e7a75b79 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb @@ -317,6 +317,14 @@ def abstract_class=(val) end end + def table_name + @table_name || name.downcase.pluralize + end + + def table_name=(name) + @table_name = name + end + def composed_of(name, opts = {}) reflection = Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts) if reflection.klass < ActiveRecord::Base diff --git a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb index 0cddc3367..d2c03a3ab 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/instance_methods.rb @@ -126,8 +126,8 @@ def revert @backing_record.revert end - def changed? - @backing_record.changed? + def changed?(attr = nil) + @backing_record.changed?(*attr) end def dup diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb index 5645b687d..b231103dc 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb @@ -38,6 +38,7 @@ class Base attr_accessor :aggregate_owner attr_accessor :aggregate_attribute attr_accessor :destroyed + attr_accessor :being_destroyed attr_accessor :updated_during attr_accessor :synced_attributes attr_accessor :virgin diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb index 2188a4286..27f30b903 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb @@ -141,6 +141,7 @@ class TestModel < ApplicationRecord =end + def sync_scopes(broadcast) # record_with_current_values will return nil if data between # the broadcast record and the value on the client is out of sync @@ -336,26 +337,25 @@ def reload_from_db(force = nil) end def observed - return if @observing || ReactiveRecord::Base.data_loading? + return self if @observing || ReactiveRecord::Base.data_loading? begin @observing = true link_to_parent reload_from_db(true) if @out_of_date Hyperstack::Internal::State::Variable.get(self, :collection) + self ensure @observing = false end end - def set_count_state(val) + def count_state=(val) unless ReactiveRecord::WhileLoading.observed? Hyperstack::Internal::State::Variable.set(self, :collection, collection, true) end @count = val end - - def _count_internal(load_from_client) # when count is called on a leaf, count_internal is called for each # ancestor. Only the outermost count has load_from_client == true @@ -557,7 +557,7 @@ def internal_replace(new_array) notify_of_change new_array end - def delete(item) + def destroy_non_habtm(item) Hyperstack::Internal::State::Mapper.bulk_update do unsaved_children.delete(item) if @owner && @association @@ -573,6 +573,23 @@ def delete(item) end end + def destroy(item) + return destroy_non_habtm(item) unless @association&.habtm? + + ta = @association.through_association + item_foreign_key = @association.source_belongs_to_association.association_foreign_key + join_record = ta.klass.find_by( + ta.association_foreign_key => @owner.id, + item_foreign_key => item.id + ) + return destroy_non_habtm(item) if join_record.nil? || + join_record.backing_record.being_destroyed + + join_record&.destroy + end + + alias delete destroy + def delete_internal(item) if collection all.delete(item) diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index 6b2b24e7c..cce04f31f 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -230,7 +230,7 @@ def acts_as_string? def respond_to?(method) return true if method == :acts_as_string? return true if %i[inspect to_date to_f to_i to_numeric to_number to_s to_time].include? method - return @object.respond_to? if @object + return @object.respond_to?(method) if @object false end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb index c841192a9..e7cbe756f 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb @@ -457,7 +457,7 @@ def self.save_records(models, associations, acting_user, validate, save) parent.send("#{association[:attribute]}=", aggregate) #puts "updated is frozen? #{aggregate.frozen?}, parent attributes = #{parent.send(association[:attribute]).attributes}" elsif parent.class.reflect_on_association(association[:attribute].to_sym).nil? - raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?" + raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?" elsif parent.class.reflect_on_association(association[:attribute].to_sym).collection? #puts ">>>>>>>>>> #{parent.class.name}.send('#{association[:attribute]}') << #{reactive_records[association[:child_id]]})" dont_save_list.delete(parent) @@ -549,13 +549,11 @@ def self.save_records(models, associations, acting_user, validate, save) if RUBY_ENGINE == 'opal' def destroy(&block) + return if @destroyed || @being_destroyed - return if @destroyed - - #destroy_associations + # destroy_associations promise = Promise.new - if !data_loading? && (id || vector) Operations::Destroy.run(model: ar_instance.model_name.to_s, id: id, vector: vector) .then do |response| @@ -567,7 +565,7 @@ def destroy(&block) destroy_associations # sync_unscoped_collection! # ? should we do this here was NOT being done before hypermesh integration yield true, nil if block - promise.resolve({success: true}) + promise.resolve(success: true) end # DO NOT CLEAR ATTRIBUTES. Records that are not found, are destroyed, and if they are searched for again, we want to make @@ -587,18 +585,16 @@ def destroy(&block) def self.destroy_record(model, id, vector, acting_user) model = Object.const_get(model) record = if id - model.find(id) - else - ServerDataCache.new(acting_user, {})[*vector] - end - + model.find(id) + else + ServerDataCache.new(acting_user, {})[*vector].value + end record.check_permission_with_acting_user(acting_user, :destroy_permitted?).destroy - {success: true, attributes: {}} - + { success: true, attributes: {} } rescue Exception => e - #ReactiveRecord::Pry.rescued(e) - {success: false, record: record, message: e} + # ReactiveRecord::Pry.rescued(e) + { success: false, record: record, message: e } end end end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb index a141bf7b3..3b1f73c87 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb @@ -93,7 +93,7 @@ class Save < Base class Destroy < Base param :acting_user, nils: true param :model - param :id + param :id, nils: true param :vector step do ReactiveRecord::Base.destroy_record( diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb index a3458e12b..52fecd72c 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb @@ -94,7 +94,7 @@ def set_common(attr, value) end def set_attribute_change_status_and_notify(attr, changed, new_value) - if @virgin + if @virgin || @being_destroyed @attributes[attr] = new_value else change_status_and_notify_helper(attr, changed) do |had_key, current_value| @@ -108,14 +108,13 @@ def set_attribute_change_status_and_notify(attr, changed, new_value) end def set_change_status_and_notify_only(attr, changed) - return if @virgin + return if @virgin || @being_destroyed change_status_and_notify_helper(attr, changed) do Hyperstack::Internal::State::Variable.set(self, attr, nil) unless data_loading? end end def change_status_and_notify_helper(attr, changed) - return if @being_destroyed empty_before = changed_attributes.empty? if !changed || data_loading? changed_attributes.delete(attr) diff --git a/ruby/hyper-model/lib/reactive_record/broadcast.rb b/ruby/hyper-model/lib/reactive_record/broadcast.rb index bf3214818..944fd7a51 100644 --- a/ruby/hyper-model/lib/reactive_record/broadcast.rb +++ b/ruby/hyper-model/lib/reactive_record/broadcast.rb @@ -115,6 +115,10 @@ def destroyed? @destroyed end + def local? + @is_local + end + def klass Object.const_get(@klass) end @@ -135,7 +139,7 @@ def self.in_transit @in_transit ||= Hash.new { |h, k| h[k] = new(k) } end - def initialize(id) + def initialize(id = nil) @id = id @received = Set.new @record = {} @@ -144,6 +148,7 @@ def initialize(id) def local(operation, record, data) @destroyed = operation == :destroy + @is_local = true @is_new = operation == :create @klass = record.class.name @record = data @@ -166,7 +171,7 @@ def receive(params) @backing_record = ReactiveRecord::Base.exists?(klass, params.record[klass.primary_key]) # first check to see if we already destroyed it and if so exit the block - return if @backing_record&.destroyed + break if @backing_record&.destroyed # We ignore whether the record is being created or not, and just check and see if in our # local copy we have ever loaded this id before. If we have then its not new to us. diff --git a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb index 002075915..23a709a02 100644 --- a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb +++ b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb @@ -486,7 +486,7 @@ def self.load_from_json(tree, target = nil) elsif !target load_from_json(value, Object.const_get(method)) elsif method == "*count" - target.set_count_state(value.first) + target.count_state = value.first elsif method.is_a? Integer or method =~ /^[0-9]+$/ new_target = target.push_and_update_belongs_to(method) elsif method.is_a? Array diff --git a/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb new file mode 100644 index 000000000..f5671dcbe --- /dev/null +++ b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' +require 'test_components' + +describe "has_and_belongs_to_many", js: true do + + alias_method :on_client, :evaluate_ruby + + before(:all) do + require 'pusher' + require 'pusher-fake' + Pusher.app_id = "MY_TEST_ID" + Pusher.key = "MY_TEST_KEY" + Pusher.secret = "MY_TEST_SECRET" + require "pusher-fake/support/base" + + Hyperstack.configuration do |config| + config.transport = :pusher + config.channel_prefix = "synchromesh" + config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options) + end + + class ActiveRecord::Base + class << self + def public_columns_hash + @public_columns_hash ||= {} + end + end + end + + class Physician < ActiveRecord::Base + def self.build_tables + connection.create_table :physicians, force: true do |t| + t.string :name + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + class Patient < ActiveRecord::Base + def self.build_tables + connection.create_table :patients, force: true do |t| + t.string :name + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + class PatientsPhysicianStub < ActiveRecord::Base + def self.build_tables + connection.create_table :patients_physicians, force: true do |t| + t.belongs_to :physician, index: true + t.belongs_to :patient, index: true + end + end + end + + Physician.build_tables #rescue nil + PatientsPhysicianStub.build_tables #rescue nil + Patient.build_tables #rescue nil + + isomorphic do + class Physician < ActiveRecord::Base + has_and_belongs_to_many :patients + end + + class Patient < ActiveRecord::Base + has_and_belongs_to_many :physicians + end + end + end + + before(:each) do + stub_const 'ApplicationPolicy', Class.new + ApplicationPolicy.class_eval do + always_allow_connection + regulate_all_broadcasts { |policy| policy.send_all } + allow_change(to: :all, on: [:create, :update, :destroy]) { true } + end + + size_window(:medium) + end + + it 'works' do + mccoy = Physician.create(name: 'Dr. McCoy') + Patient.create(name: 'James T. Kirk').physicians << mccoy + expect { Physician.first.patients.count }.on_client_to eq(1) + + Patient.create(name: 'Spock').physicians << mccoy + expect { Physician.first.patients.count }.on_client_to eq(2) + + on_client { Patient.create(name: 'Uhuru') } + 2.times do + on_client { Patient.find(3).physicians << Physician.first } + expect { Patient.find(3).physicians.count }.on_client_to eq(1) + wait_for { Patient.find(3).physicians.count }.to eq(1) + expect { Physician.first.patients.count }.on_client_to eq(3) + expect(Physician.first.patients.count).to eq(3) + + on_client { Patient.find(3).physicians.destroy(Physician.first) } + expect { Patient.find(3).physicians.count }.on_client_to eq(0) + wait_for { Patient.find(3).physicians.count }.to eq(0) + expect { Physician.first.patients.count }.on_client_to eq(2) + expect(Physician.first.patients.count).to eq(2) + end + + on_client { Patient.find(3).physicians.delete(Physician.first) } + expect { Patient.find(3).physicians.count }.on_client_to eq(0) + expect(Patient.find(3).physicians.count).to eq(0) + end +end diff --git a/ruby/hyper-model/spec/spec_helper.rb b/ruby/hyper-model/spec/spec_helper.rb index 1cf662002..b860162e1 100644 --- a/ruby/hyper-model/spec/spec_helper.rb +++ b/ruby/hyper-model/spec/spec_helper.rb @@ -243,7 +243,7 @@ class ActiveRecord::Base end end - config.before(:each, :js => true) do + config.before(:each, js: true) do DatabaseCleaner.strategy = :truncation end diff --git a/ruby/hyper-model/spec/test_app/config/application.rb b/ruby/hyper-model/spec/test_app/config/application.rb index a9a04996a..864804bf0 100644 --- a/ruby/hyper-model/spec/test_app/config/application.rb +++ b/ruby/hyper-model/spec/test_app/config/application.rb @@ -17,7 +17,7 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check_enabled = false config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-model/spec/test_components.rb b/ruby/hyper-model/spec/test_components.rb index 775e16433..133e477cb 100644 --- a/ruby/hyper-model/spec/test_components.rb +++ b/ruby/hyper-model/spec/test_components.rb @@ -1,6 +1,6 @@ RSpec.configure do |config| config.before(:all) do - on_client do + before_mount do class TestComponent < HyperComponent param scope: :all render(DIV) do diff --git a/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb b/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb index 4f93c4ba8..54b185d88 100644 --- a/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb +++ b/ruby/hyper-operation/lib/hyper-operation/transport/client_drivers.rb @@ -97,7 +97,7 @@ def self.connect_to(channel_name, id = nil) %x{ var channel = #{ClientDrivers.opts[:pusher_api]}.subscribe(#{channel.gsub('::', '==')}); channel.bind('dispatch', #{ClientDrivers.opts[:dispatch]}) - channel.bind('pusher:subscription_succeeded', #{lambda {ClientDrivers.get_queued_data("connect-to-transport", channel_string)}}) + channel.bind('pusher:subscription_succeeded', #{->(*) { ClientDrivers.get_queued_data("connect-to-transport", channel_string)}}) } @pusher_dispatcher_registered = true elsif ClientDrivers.opts[:transport] == :action_cable diff --git a/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb b/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb index c39ad20a1..7d745c1bf 100644 --- a/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb +++ b/ruby/hyper-state/lib/hyperstack/internal/state/variable.rb @@ -13,7 +13,9 @@ def get(obj, name) map_object[0] end - def set(obj, name, value) + def set(obj, name, value, _x = nil) + # _x is some legacy function, which I think queued up state changes to the end + # which is perhaps now the default. map_object = legacy_map[obj][name] map_object[0] = value Hyperstack::Internal::State::Mapper.mutated!(map_object.object_id) diff --git a/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb b/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb index 2bf6ec316..134437a0e 100644 --- a/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb +++ b/ruby/hyper-trace/lib/hyper_trace/hyper_trace.rb @@ -6,17 +6,7 @@ def hyper_trace(*args, &block) alias hypertrace hyper_trace end -class Method - def parameters - /.*function[^(]*\(([^)]*)\)/ - .match(`#{@method}.toString()`)[1] - .split(',') - .collect { |param| [:req, param.strip.to_sym] } - end -end - module HyperTrace - class Config def initialize(klass, instrument_class, opts, &block) @klass = klass @@ -116,15 +106,11 @@ def exclusions def instrumentation_off(config) if config.instrument_class? config.klass.methods.grep(/^__hyper_trace_pre_.+$/).each do |method| - config.klass.class_eval do - class << self - alias_method method.gsub(/^__hyper_trace_pre_/, ''), method - end - end + config.klass.singleton_class.alias_method method.gsub(/^__hyper_trace_pre_/, ''), method end else config.klass.instance_methods.grep(/^__hyper_trace_pre_.+$/).each do |method| - config.klass.class_eval { alias_method method.gsub(/^__hyper_trace_pre_/, ''), method } + config.klass.alias_method method.gsub(/^__hyper_trace_pre_/, ''), method end end end @@ -145,17 +131,12 @@ def all_methods(config) def instrument_method(method, config) if config.instrument_class? - config.klass.class_eval do - class << self - alias_method "__hyper_trace_pre_#{method}", method unless method_defined? "__pre_hyper_trace_#{method}" - end + unless config.klass.singleton_class.method_defined? "__pre_hyper_trace_#{method}" # is this right? + config.klass.singleton_class.alias_method "__hyper_trace_pre_#{method}", method end - add_hyper_trace_method(method, config) else unless config.klass.method_defined? "__pre_hyper_trace_#{method}" - config.klass.class_eval do - alias_method "__hyper_trace_pre_#{method}", method - end + config.klass.alias_method "__hyper_trace_pre_#{method}", method end end add_hyper_trace_method(method, config) @@ -194,28 +175,77 @@ def instance_tag(instance, prefix = ' - ') end end + def required(arity, actual_length) + if arity.negative? + req_count = - arity - 1 + raise ArgumentError, "missing required arguments: #{actual_length} for #{req_count}" if req_count > actual_length + else + req_count = arity + raise ArgumentError, "incorrect number of arguments, expected #{req_count} got #{actual_length}" if req_count != actual_length + end + req_count + end + + # [["req", "x"], ["opt", "y"], ["rest"], ["keyreq", "z"], ["key", "opt"], ["block", "flop"]] + + def map_parameters(instance, name, actual, format = true) + method = instance.method("__hyper_trace_pre_#{name}") + map_with_specs(method, actual.dup, format) || map_without_specs(actual, format) + end + + def key?(actual, key) + actual&.last&.respond_to?(:key?) && actual.last.key?(key) + end + + def map_with_specs(method, actual, show_blank_rest) + specs = method.parameters + req_count = required(method.arity, actual.length) + args = {} + + specs.each do |type, key| + case type + when :req + args[key] = actual.shift + req_count -= 1 + when :opt + args[key] = actual.shift if actual.length > req_count + when (key || show_blank_rest) && :rest + args[key || '*'] = [*actual[0..-req_count - 1]] + when :keyreq + raise ArgumentError, "missing keyword argument: #{key}" unless key?(actual, key) + + args[key] = actual.last[key] + when key?(actual, key) && :key + args[key] = actual.last[key] + end + end + args + rescue ArgumentError, Interrupt, SignalException => e + raise e + rescue Exception => e + nil + end + + def map_without_specs(actual, format) + args = {} + prefix = 'p' unless format + actual.each_with_index { |value, i| args["#{prefix}#{i + 1}"] = value } + args + end + def format_head(instance, name, args, &block) @formatting = true - method = instance.method("__hyper_trace_pre_#{name}") if args.any? group(" #{name}(...)#{instance_tag(instance)}") do - params = method.parameters - group("args:", collapsed: true) do - params.each_with_index do |param_spec, i| - arg_name = param_spec[1] - if arg_name == '$a_rest' - arg_name = '*' - arg = args[i..-1] - else - arg = args[i] - end - if safe_i(arg).length > 30 || show_js_object(arg) - group "#{arg_name}: #{safe_s(arg)}"[0..29], collapsed: true do - puts safe_i(arg) - log arg if show_js_object(arg) + group("args: (#{args.length})", collapsed: true) do + map_parameters(instance, name, args).each do |arg, value| + if safe_i(value).length > 30 || show_js_object(value) + group "#{arg}: #{safe_s(value)}"[0..29], collapsed: true do + puts safe_i(value) + log value if show_js_object(value) end else - group "#{arg_name}: #{safe_i(arg)}" + group "#{arg}: #{safe_i(value)}" end end end @@ -286,7 +316,10 @@ def format_exception(result) @formatting = true if safe_i(result).length > 40 group "raised: #{safe_s(result)}"[0..40], collapsed: true do - puts safe_i(result) + puts result.backtrace[0] + result.backtrace[1..-1].each do |line| + log line + end end else group "raised: #{safe_i(result)}" @@ -305,24 +338,23 @@ def should_break?(location, config, name, args, instance, result) def breakpoint(location, config, name, args, instance, result = nil) if should_break? location, config, name, args, instance, result - method = instance.method("__hyper_trace_pre_#{name}") + params = map_parameters(instance, name, args, false) fn_def = ['RESULT'] - fn_def += method.parameters.collect { |p| p[1] } + fn_def += params.keys fn_def += ["//break on #{location} of #{name}\nvar self = this;\ndebugger;\n;"] puts "break on #{location} of #{name}" fn = `Function.apply(#{self}, #{fn_def}).bind(#{instance})` - fn.call(result, *args) + fn.call(result, *params.values) end end - def call_original(instance, method, *args, &block) + def call_original(instance, method, args, &block) @formatting = false instance.send "__hyper_trace_pre_#{method}", *args, &block ensure @formatting = true end - def add_hyper_trace_method(method, config) def_method = config.instrument_class? ? :define_singleton_method : :define_method config.klass.send(def_method, method) do |*args, &block| @@ -330,7 +362,7 @@ def add_hyper_trace_method(method, config) if HyperTrace.formatting? begin send "__hyper_trace_pre_#{method}", *args, &block - rescue Exception + rescue StandardError "???" end else @@ -338,11 +370,13 @@ def add_hyper_trace_method(method, config) HyperTrace.format_head(self, method, args) do HyperTrace.format_instance_internal(self) HyperTrace.breakpoint(:enter, config, method, args, self) - result = HyperTrace.call_original self, method, *args, &block + result = HyperTrace.call_original self, method, args, &block HyperTrace.format_result(result) HyperTrace.breakpoint(:exit, config, method, args, self, result) result end + rescue Interrupt, SignalException => e + raise e rescue Exception => e HyperTrace.format_exception(e) debugger unless HyperTrace.exclusions[self.class][:rescue].include? :method From efdfc99cf09c5fb04d8e5c778d7e1b6527189ebd Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 15:17:30 -0500 Subject: [PATCH 157/307] reverting last travis change --- .travis.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index ba19b4098..6c0366e60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ dist: xenial -language: bash +# language: bash +# cache: +# bundler: true +# directories: +# - node_modules # NPM packages addons: apt: @@ -18,11 +22,6 @@ addons: postgresql: '11' _test_gem_pg: &_test_gem_pg - stage: test - cache: - bundler: true - directories: - - node_modules # NPM packages before_install: - echo 'installing postgresql' - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf @@ -51,10 +50,6 @@ _test_gem_pg: &_test_gem_pg _test_gem: &_test_gem stage: test - cache: - bundler: true - directories: - - node_modules # NPM packages addons: apt: sources: From 9e195725d2001d89a66381bc4ebf6000b4f7524b Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 17:25:58 -0500 Subject: [PATCH 158/307] trying to update chrome driver to latest --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6c0366e60..c51d21205 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,8 @@ _test_gem_pg: &_test_gem_pg before_script: - echo before_script $COMPONENT + - echo updating chrome driver + - chromedriver-update 89.0.4389.23 - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - bundle exec rake spec:prepare @@ -78,6 +80,8 @@ _test_gem: &_test_gem before_script: - echo before_script $COMPONENT + - echo updating chrome driver + - chromedriver-update 89.0.4389.23 - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - bundle exec rake spec:prepare From 2e413fb0615d474f7393add47b0c68b55090e07f Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 17:53:46 -0500 Subject: [PATCH 159/307] more attempts to update chromedriver --- .travis.yml | 4 ++-- ruby/hyper-model/Rakefile | 2 ++ ruby/hyperstack-config/Rakefile | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c51d21205..d006d0860 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,13 +80,13 @@ _test_gem: &_test_gem before_script: - echo before_script $COMPONENT - - echo updating chrome driver - - chromedriver-update 89.0.4389.23 - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - bundle exec rake spec:prepare - google-chrome --version - which google-chrome + - echo updating chrome driver + - bundle exec rake webdrivers:chromedriver:update - yarn install script: - echo running script $COMPONENT diff --git a/ruby/hyper-model/Rakefile b/ruby/hyper-model/Rakefile index eeb360644..622689717 100644 --- a/ruby/hyper-model/Rakefile +++ b/ruby/hyper-model/Rakefile @@ -1,5 +1,7 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" +require 'webdrivers' +load 'webdrivers/Rakefile' def run_batches(batches) failed = false diff --git a/ruby/hyperstack-config/Rakefile b/ruby/hyperstack-config/Rakefile index 17cf89f90..86c3f30eb 100644 --- a/ruby/hyperstack-config/Rakefile +++ b/ruby/hyperstack-config/Rakefile @@ -1,5 +1,7 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" +require 'webdrivers' +load 'webdrivers/Rakefile' RSpec::Core::RakeTask.new(:spec) From f817a459d3bf87fea919dfc885d017ac76854871 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 18:03:45 -0500 Subject: [PATCH 160/307] more attempts to update chromedriver 2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d006d0860..f3c640fe2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -75,7 +75,7 @@ _test_gem: &_test_gem - nvm install 10 - rvm install 2.6.3 # was 2.5.1 - gem install bundler - - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + # - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - echo 'install completed' before_script: From be4899b93b7ad2ae73109a26ece4d16c47b71598 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 22:20:38 -0500 Subject: [PATCH 161/307] another attempt to update chromedriver --- .travis.yml | 2 -- ruby/hyper-spec/lib/hyper-spec.rb | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3c640fe2..76b7d9a39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -85,8 +85,6 @@ _test_gem: &_test_gem - bundle exec rake spec:prepare - google-chrome --version - which google-chrome - - echo updating chrome driver - - bundle exec rake webdrivers:chromedriver:update - yarn install script: - echo running script $COMPONENT diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 5b802ce2e..f9f3d5e0b 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -23,7 +23,11 @@ require 'hyper-spec/expectations' require 'parser/current' -require 'selenium/web_driver/firefox/profile' if defined?(Selenium::WebDriver::Firefox) +require 'webdrivers/chromedriver' +if defined?(Selenium::WebDriver::Firefox) + require 'selenium/web_driver/firefox/profile' + require 'webdrivers/geckodriver' +end require 'selenium-webdriver' require 'hyper-spec/version' From 289fddad8f1ab58fa416ba0ee4923a8f62ddb4eb Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 22:39:35 -0500 Subject: [PATCH 162/307] another attempt to update chromedriver 2 --- ruby/hyper-spec/lib/hyper-spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index f9f3d5e0b..699662484 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -24,6 +24,7 @@ require 'parser/current' require 'webdrivers/chromedriver' +Webdrivers.logger.level = :DEBUG if defined?(Selenium::WebDriver::Firefox) require 'selenium/web_driver/firefox/profile' require 'webdrivers/geckodriver' From 42034091d81eb87c53642b748b41eaedf7ef1713 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 22:49:18 -0500 Subject: [PATCH 163/307] trying to at least get logging going --- ruby/hyper-spec/lib/hyper-spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 699662484..7a4708b76 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -25,6 +25,7 @@ require 'parser/current' require 'webdrivers/chromedriver' Webdrivers.logger.level = :DEBUG +puts 'logger level set to :DEBUG' if defined?(Selenium::WebDriver::Firefox) require 'selenium/web_driver/firefox/profile' require 'webdrivers/geckodriver' From 9ea584d70d525619369a6ac06b207fdfe366edbb Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 23:24:03 -0500 Subject: [PATCH 164/307] trying another solution --- .travis.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 76b7d9a39..2e2edb4c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,14 +8,14 @@ dist: xenial addons: apt: - sources: - - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + # sources: + # - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + # key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + # - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + # key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - postgresql-11 - - chromium-chromedriver + # - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -54,13 +54,13 @@ _test_gem: &_test_gem stage: test addons: apt: - sources: - - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + # sources: + # - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + # key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + # - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + # key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - - chromium-chromedriver + # - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -82,6 +82,7 @@ _test_gem: &_test_gem - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 + - rake webdrivers:chromedriver:version - bundle exec rake spec:prepare - google-chrome --version - which google-chrome From d50e3e740fbacc27d406a6046a0e47d405354be8 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 23:28:33 -0500 Subject: [PATCH 165/307] trying another solution 2 --- .travis.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e2edb4c6..83520f390 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,11 @@ dist: xenial addons: apt: - # sources: - # - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - # key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - # - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - # key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - postgresql-11 # - chromium-chromedriver @@ -54,11 +54,11 @@ _test_gem: &_test_gem stage: test addons: apt: - # sources: - # - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' - # key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' - # - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' - # key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' + sources: + - sourceline: 'deb http://dl.yarnpkg.com/debian/ stable main' + key_url: 'http://dl.yarnpkg.com/debian/pubkey.gpg' + - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' + key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: # - chromium-chromedriver - google-chrome-stable From 70fbf5d097c9b4dc3c0c72f55f45d429f3bfe5db Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 23:38:36 -0500 Subject: [PATCH 166/307] trying another solution 3 --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 83520f390..7100d7d47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ addons: key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - postgresql-11 - # - chromium-chromedriver + - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -60,7 +60,7 @@ _test_gem: &_test_gem - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - # - chromium-chromedriver + - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -75,6 +75,7 @@ _test_gem: &_test_gem - nvm install 10 - rvm install 2.6.3 # was 2.5.1 - gem install bundler + # /usr/lib/chromium-browser/chromedriver # - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - echo 'install completed' From 4f5ee675311d037ee153cb49217d6c2100a88a6f Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 5 Mar 2021 23:58:14 -0500 Subject: [PATCH 167/307] trying another solution 4 --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7100d7d47..d98c349e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -75,7 +75,7 @@ _test_gem: &_test_gem - nvm install 10 - rvm install 2.6.3 # was 2.5.1 - gem install bundler - # /usr/lib/chromium-browser/chromedriver + # /usr/lib/chromium-browser/chromedriver <- is that a dir of a file? # - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - echo 'install completed' @@ -84,6 +84,7 @@ _test_gem: &_test_gem - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - rake webdrivers:chromedriver:version + - ls $WD_INSTALL_DIR - bundle exec rake spec:prepare - google-chrome --version - which google-chrome @@ -114,7 +115,7 @@ jobs: - <<: *_test_gem env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 WD_INSTALL_DIR="/usr/lib/chromium-browser/" - <<: *_test_gem env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - <<: *_test_gem From 225605c0d0c86f584115146764a25bd130955413 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 00:08:44 -0500 Subject: [PATCH 168/307] trying another solution 5 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d98c349e8..5c0a575bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,6 +84,8 @@ _test_gem: &_test_gem - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - rake webdrivers:chromedriver:version + - rake webdrivers:chromedriver:update + - rake webdrivers:chromedriver:version - ls $WD_INSTALL_DIR - bundle exec rake spec:prepare - google-chrome --version From d439a5fb2a189c782fe1eedbda7e29f826c6b771 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 00:16:12 -0500 Subject: [PATCH 169/307] trying another solution 6 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c0a575bc..dd205fa08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,7 @@ _test_gem: &_test_gem - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - rake webdrivers:chromedriver:version - - rake webdrivers:chromedriver:update + - sudo rake webdrivers:chromedriver:update - rake webdrivers:chromedriver:version - ls $WD_INSTALL_DIR - bundle exec rake spec:prepare @@ -117,7 +117,7 @@ jobs: - <<: *_test_gem env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 WD_INSTALL_DIR="/usr/lib/chromium-browser/" + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 WD_INSTALL_DIR="/usr/lib/chromium-browser" - <<: *_test_gem env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - <<: *_test_gem From 8c52aa14a310752f92eaba5ce265b72e999731e3 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 00:24:08 -0500 Subject: [PATCH 170/307] trying another solution 7 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index dd205fa08..8151809f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,9 +83,9 @@ _test_gem: &_test_gem - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - - rake webdrivers:chromedriver:version - - sudo rake webdrivers:chromedriver:update - - rake webdrivers:chromedriver:version + - bundle exec rake webdrivers:chromedriver:version + - bundle exec sudo rake webdrivers:chromedriver:update + - bundle exec rake webdrivers:chromedriver:version - ls $WD_INSTALL_DIR - bundle exec rake spec:prepare - google-chrome --version From c592aac939352eef7146a6b85d83b8d432fb4611 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 00:25:00 -0500 Subject: [PATCH 171/307] trying another solution 8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8151809f1..6270122c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,7 @@ _test_gem: &_test_gem - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - bundle exec rake webdrivers:chromedriver:version - - bundle exec sudo rake webdrivers:chromedriver:update + - sudo bundle exec rake webdrivers:chromedriver:update - bundle exec rake webdrivers:chromedriver:version - ls $WD_INSTALL_DIR - bundle exec rake spec:prepare From d425b8d08c8ade90de3a484188e7e06c24a39074 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 00:44:53 -0500 Subject: [PATCH 172/307] trying another solution 9 --- .travis.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6270122c9..3b18bbbc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,13 +83,17 @@ _test_gem: &_test_gem - echo before_script $COMPONENT - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 + - google-chrome --version + - which google-chrome - bundle exec rake webdrivers:chromedriver:version - - sudo bundle exec rake webdrivers:chromedriver:update + - bundle exec rake webdrivers:chromedriver:update + - ls -la ~/bin/.webdrivers + - sudo ln -s ~/bin/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver - bundle exec rake webdrivers:chromedriver:version - - ls $WD_INSTALL_DIR + - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' + - ls /usr/lib/chromium-browser - bundle exec rake spec:prepare - - google-chrome --version - - which google-chrome + - yarn install script: - echo running script $COMPONENT @@ -117,7 +121,7 @@ jobs: - <<: *_test_gem env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 WD_INSTALL_DIR="/usr/lib/chromium-browser" + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 WD_INSTALL_DIRX="/usr/lib/chromium-browser" - <<: *_test_gem env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - <<: *_test_gem From d025b0376145db87cdb0dce19207e7a75df5fc61 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 00:52:07 -0500 Subject: [PATCH 173/307] trying another solution 10 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b18bbbc9..763187663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,8 +87,8 @@ _test_gem: &_test_gem - which google-chrome - bundle exec rake webdrivers:chromedriver:version - bundle exec rake webdrivers:chromedriver:update - - ls -la ~/bin/.webdrivers - - sudo ln -s ~/bin/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver + - ls -la ~/.webdrivers + - sudo ln -s ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver - bundle exec rake webdrivers:chromedriver:version - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' - ls /usr/lib/chromium-browser From 17370456ed3c34a37bd36400bdd78e0a8b8b7789 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 01:02:17 -0500 Subject: [PATCH 174/307] trying another solution 11 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 763187663..450be2d0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,7 +88,7 @@ _test_gem: &_test_gem - bundle exec rake webdrivers:chromedriver:version - bundle exec rake webdrivers:chromedriver:update - ls -la ~/.webdrivers - - sudo ln -s ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver + - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver - bundle exec rake webdrivers:chromedriver:version - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' - ls /usr/lib/chromium-browser From 264e54cd89a48f533b764395213dc16d3be317ff Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 07:58:28 -0500 Subject: [PATCH 175/307] cleaning up travis --- .travis.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 450be2d0d..8dbad4baa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ addons: key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - postgresql-11 - - chromium-chromedriver + # - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -60,7 +60,7 @@ _test_gem: &_test_gem - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - - chromium-chromedriver + # - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -77,6 +77,7 @@ _test_gem: &_test_gem - gem install bundler # /usr/lib/chromium-browser/chromedriver <- is that a dir of a file? # - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver + - sudo ln -s /usr/lib/chromium-browser ~/.webdrivers - echo 'install completed' before_script: @@ -85,12 +86,13 @@ _test_gem: &_test_gem - bundle install --jobs=3 --retry=3 - google-chrome --version - which google-chrome - - bundle exec rake webdrivers:chromedriver:version - - bundle exec rake webdrivers:chromedriver:update - - ls -la ~/.webdrivers - - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver - - bundle exec rake webdrivers:chromedriver:version - - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' + - bundle exec ruby -e 'require "webdrivers"; Webdrivers::Chromedriver.update; puts Webdrivers::Chromedriver.current_version' + # - bundle exec rake webdrivers:chromedriver:version + # - bundle exec rake webdrivers:chromedriver:update + # - ls -la ~/.webdrivers + # - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver + # - bundle exec rake webdrivers:chromedriver:version + # - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' - ls /usr/lib/chromium-browser - bundle exec rake spec:prepare From 444dcd0d91006deec014c40cd9fdcd9272308343 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 08:07:52 -0500 Subject: [PATCH 176/307] cleaning up travis 2 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dbad4baa..1278590f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,7 +77,7 @@ _test_gem: &_test_gem - gem install bundler # /usr/lib/chromium-browser/chromedriver <- is that a dir of a file? # - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - - sudo ln -s /usr/lib/chromium-browser ~/.webdrivers + # - sudo ln -s /usr/lib/chromium-browser ~/.webdrivers - echo 'install completed' before_script: @@ -90,7 +90,7 @@ _test_gem: &_test_gem # - bundle exec rake webdrivers:chromedriver:version # - bundle exec rake webdrivers:chromedriver:update # - ls -la ~/.webdrivers - # - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver + - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver # - bundle exec rake webdrivers:chromedriver:version # - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' - ls /usr/lib/chromium-browser From 2b86f31c47a4200e2c5770694dbc3783fa537eb1 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 08:15:52 -0500 Subject: [PATCH 177/307] cleaning up travis 3 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1278590f8..4d24c749f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ addons: key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - postgresql-11 - # - chromium-chromedriver + - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -60,7 +60,7 @@ _test_gem: &_test_gem - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - # - chromium-chromedriver + - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -89,7 +89,7 @@ _test_gem: &_test_gem - bundle exec ruby -e 'require "webdrivers"; Webdrivers::Chromedriver.update; puts Webdrivers::Chromedriver.current_version' # - bundle exec rake webdrivers:chromedriver:version # - bundle exec rake webdrivers:chromedriver:update - # - ls -la ~/.webdrivers + - ls -la ~/.webdrivers - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver # - bundle exec rake webdrivers:chromedriver:version # - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' From 392138892f68ab4b8941864134a9cdf4ae286135 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 08:23:42 -0500 Subject: [PATCH 178/307] cleaning up travis 4 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d24c749f..147f1e156 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ addons: key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - postgresql-11 - - chromium-chromedriver + # - chromium-chromedriver - google-chrome-stable - yarn - redis-server @@ -39,7 +39,7 @@ _test_gem_pg: &_test_gem_pg before_script: - echo before_script $COMPONENT - echo updating chrome driver - - chromedriver-update 89.0.4389.23 + # - chromedriver-update 89.0.4389.23 - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - bundle exec rake spec:prepare @@ -60,8 +60,8 @@ _test_gem: &_test_gem - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - - chromium-chromedriver - - google-chrome-stable + # - chromium-chromedriver + # - google-chrome-stable - yarn - redis-server mariadb: '10.3' From 4dd0fe981b344b7e58c77e52b774ca0470f0a07e Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 08:28:50 -0500 Subject: [PATCH 179/307] cleaning up travis 5 --- .travis.yml | 2 +- ruby/hyper-spec/lib/hyper-spec.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 147f1e156..08946bad3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ addons: key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - postgresql-11 - # - chromium-chromedriver + - chromium-chromedriver - google-chrome-stable - yarn - redis-server diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 7a4708b76..f9f3d5e0b 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -24,8 +24,6 @@ require 'parser/current' require 'webdrivers/chromedriver' -Webdrivers.logger.level = :DEBUG -puts 'logger level set to :DEBUG' if defined?(Selenium::WebDriver::Firefox) require 'selenium/web_driver/firefox/profile' require 'webdrivers/geckodriver' From a183c7fe06e42602b85efe741c5c94cee7eab9e7 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 08:38:44 -0500 Subject: [PATCH 180/307] cleaning up travis 6 --- .travis.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08946bad3..8e7e0bdd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,9 +39,11 @@ _test_gem_pg: &_test_gem_pg before_script: - echo before_script $COMPONENT - echo updating chrome driver - # - chromedriver-update 89.0.4389.23 - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 + - bundle exec ruby -e 'require "webdrivers"; Webdrivers::Chromedriver.update; puts Webdrivers::Chromedriver.current_version' + - ls -la ~/.webdrivers + - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver - bundle exec rake spec:prepare - google-chrome --version - which google-chrome @@ -60,8 +62,8 @@ _test_gem: &_test_gem - sourceline: 'deb http://dl.google.com/linux/chrome/deb/ stable main' key_url: 'https://dl-ssl.google.com/linux/linux_signing_key.pub' packages: - # - chromium-chromedriver - # - google-chrome-stable + - chromium-chromedriver + - google-chrome-stable - yarn - redis-server mariadb: '10.3' @@ -85,7 +87,7 @@ _test_gem: &_test_gem - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - google-chrome --version - - which google-chrome + #- which google-chrome - bundle exec ruby -e 'require "webdrivers"; Webdrivers::Chromedriver.update; puts Webdrivers::Chromedriver.current_version' # - bundle exec rake webdrivers:chromedriver:version # - bundle exec rake webdrivers:chromedriver:update @@ -93,7 +95,7 @@ _test_gem: &_test_gem - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver # - bundle exec rake webdrivers:chromedriver:version # - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' - - ls /usr/lib/chromium-browser + #- ls /usr/lib/chromium-browser - bundle exec rake spec:prepare - yarn install From 23393f7f7611eea60c559b8874f70e3f3be5865e Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 08:51:12 -0500 Subject: [PATCH 181/307] trying to get bundle caching working --- .travis.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8e7e0bdd6..0df61e2d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,5 @@ dist: xenial -# language: bash -# cache: -# bundler: true -# directories: -# - node_modules # NPM packages - addons: apt: sources: @@ -22,6 +16,14 @@ addons: postgresql: '11' _test_gem_pg: &_test_gem_pg + stage: test + + language: ruby + cache: + bundler: true + directories: + - node_modules # NPM packages + before_install: - echo 'installing postgresql' - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf @@ -54,6 +56,13 @@ _test_gem_pg: &_test_gem_pg _test_gem: &_test_gem stage: test + + language: ruby + cache: + bundler: true + directories: + - node_modules # NPM packages + addons: apt: sources: From 690517a3ba9efbf9fad3f3947fd9f972b5f3472e Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 09:32:31 -0500 Subject: [PATCH 182/307] final (hopefully) cleanups to travis chrome issue --- .travis.yml | 14 ++------------ ruby/hyper-model/Rakefile | 2 -- ruby/hyper-spec/lib/hyper-spec.rb | 2 -- ruby/hyperstack-config/Rakefile | 2 -- 4 files changed, 2 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0df61e2d7..65497785a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ _test_gem_pg: &_test_gem_pg _test_gem: &_test_gem stage: test - + language: ruby cache: bundler: true @@ -86,9 +86,6 @@ _test_gem: &_test_gem - nvm install 10 - rvm install 2.6.3 # was 2.5.1 - gem install bundler - # /usr/lib/chromium-browser/chromedriver <- is that a dir of a file? - # - ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver - # - sudo ln -s /usr/lib/chromium-browser ~/.webdrivers - echo 'install completed' before_script: @@ -96,17 +93,10 @@ _test_gem: &_test_gem - cd ruby/$COMPONENT - bundle install --jobs=3 --retry=3 - google-chrome --version - #- which google-chrome - bundle exec ruby -e 'require "webdrivers"; Webdrivers::Chromedriver.update; puts Webdrivers::Chromedriver.current_version' - # - bundle exec rake webdrivers:chromedriver:version - # - bundle exec rake webdrivers:chromedriver:update - ls -la ~/.webdrivers - sudo cp ~/.webdrivers/chromedriver /usr/lib/chromium-browser/chromedriver - # - bundle exec rake webdrivers:chromedriver:version - # - bundle exec rake webdrivers:chromedriver:version WD_INSTALL_DIR='/usr/lib/chromium-browser' - #- ls /usr/lib/chromium-browser - bundle exec rake spec:prepare - - yarn install script: - echo running script $COMPONENT @@ -134,7 +124,7 @@ jobs: - <<: *_test_gem env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1 - <<: *_test_gem - env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 WD_INSTALL_DIRX="/usr/lib/chromium-browser" + env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1 - <<: *_test_gem env: COMPONENT=hyper-state RUBY_VERSION=2.5.1 - <<: *_test_gem diff --git a/ruby/hyper-model/Rakefile b/ruby/hyper-model/Rakefile index 622689717..eeb360644 100644 --- a/ruby/hyper-model/Rakefile +++ b/ruby/hyper-model/Rakefile @@ -1,7 +1,5 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" -require 'webdrivers' -load 'webdrivers/Rakefile' def run_batches(batches) failed = false diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index f9f3d5e0b..67b830366 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -23,10 +23,8 @@ require 'hyper-spec/expectations' require 'parser/current' -require 'webdrivers/chromedriver' if defined?(Selenium::WebDriver::Firefox) require 'selenium/web_driver/firefox/profile' - require 'webdrivers/geckodriver' end require 'selenium-webdriver' diff --git a/ruby/hyperstack-config/Rakefile b/ruby/hyperstack-config/Rakefile index 86c3f30eb..17cf89f90 100644 --- a/ruby/hyperstack-config/Rakefile +++ b/ruby/hyperstack-config/Rakefile @@ -1,7 +1,5 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" -require 'webdrivers' -load 'webdrivers/Rakefile' RSpec::Core::RakeTask.new(:spec) From 013af18b350478d1a10b17d5f33848635ab12277 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 6 Mar 2021 14:56:58 -0500 Subject: [PATCH 183/307] fixed hyper-spec rspec-steps interaction --- ruby/hyper-spec/lib/hyper-spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ruby/hyper-spec/lib/hyper-spec.rb b/ruby/hyper-spec/lib/hyper-spec.rb index 67b830366..a2492f324 100644 --- a/ruby/hyper-spec/lib/hyper-spec.rb +++ b/ruby/hyper-spec/lib/hyper-spec.rb @@ -117,6 +117,11 @@ def reset_sessions! end config.after(:all) do HyperSpec.reset_sessions! unless HyperSpec.reset_between_examples? + # If rspecs step is used first in a file, it will NOT call config.before(:all) causing the + # reset_between_examples stack to be mismatched, so we check, if its already empty we + # just leave. + next if HyperSpec.reset_between_examples.empty? + RSpec.configuration.reset_between_examples = HyperSpec.reset_between_examples.pop end config.before(:each) do |example| From d68e36655ee1cb57bdc479d4963cba4c36cd3183 Mon Sep 17 00:00:00 2001 From: R Mitchell VanDuyn Date: Tue, 9 Mar 2021 11:26:52 -0500 Subject: [PATCH 184/307] closes #371 --- .rubocop.yml | 24 ++++-- .../internal/component/rendering_context.rb | 77 +++++++++++++------ .../spec/client_features/component_spec.rb | 29 ++++--- ruby/hyper-model/spec/test_app/db/schema.rb | 32 -------- 4 files changed, 93 insertions(+), 69 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 805c0fb68..585f1cfd8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,4 @@ AllCops: - TargetRubyVersion: 2.3 Exclude: - 'db/schema.rb' - 'db/seeds.rb' @@ -31,14 +30,11 @@ Metrics/BlockLength: Description: 'Avoid long blocks with many lines.' Enabled: false -Metrics/LineLength: - Max: 100 - ## Performance -Performance/RegexpMatch: - Enabled: false +# Performance/RegexpMatch: +# Enabled: false ## Style @@ -139,3 +135,19 @@ Style/MutableConstant: Style/SafeNavigation: Enabled: false + +Style/StringLiterals: + EnforcedStyle: double_quotes + Exclude: + - Gemfile + +Style/FrozenStringLiteralComment: + Enabled: false + Exclude: + - app/hyperstack/components + - app/hyperstack/libs + +Layout/MultilineMethodCallIndentation: + Exclude: + - 'bin/yarn' + - app/hyperstack/components diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb index 145ccd6c6..f6dba2817 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb @@ -91,12 +91,12 @@ def remove_nodes_from_args(args) # two child Elements will be generated. # # the final value of the block should either be - # 1 an object that responds to :acts_as_string? - # 2 a string, - # 3 an element that is NOT yet pushed on the rendering buffer - # 4 or the last element pushed on the buffer + # 1 a hyper model dummy value that is being loaded, + # 2 a string (or if the buffer is empty any value to which to_s can be applied) + # 3 an Element that is NOT yet pushed on the rendering buffer + # 4 or the last Element pushed on the buffer # - # in case 1 we render a span + # in case 1 we render a span wrapping the dummy value # in case 2 we automatically push the string onto the buffer # in case 3 we also push the Element onto the buffer IF the buffer is empty # case 4 requires no special processing @@ -105,31 +105,62 @@ def remove_nodes_from_args(args) # outer rendering scope. In this case react only allows us to generate 1 Element # so we insure that is the case, and also check to make sure that element in the buffer # is the element returned + # + # Note that the reason we only allow Strings to be automatically pushed is to avoid + # confusing results in situations like this: + # DIV { collection.each { |item| SPAN { item } } } + # If we accepted any object to be rendered this would generate: + # DIV { SPAN { collection[0] } SPAN { collection[n] } collection.to_s } + # which is probably not the desired output. If it was you would just append to_s + # to the end of the expression, to force it to be added to the output buffer. + # + # However if the buffer is empty then it makes sense to automatically apply the `.to_s` + # to the value, and push it on the buffer def run_child_block(is_outer_scope) result = yield - if result.respond_to?(:acts_as_string?) && result.acts_as_string? - # hyper-mesh DummyValues respond to acts_as_string, and must + if dummy_value?(result) + # hyper-mesh DummyValues must # be converted to spans INSIDE the parent, otherwise the waiting_on_resources # flag will get set in the wrong context RenderingContext.render(:span) { result.to_s } - elsif result.is_a?(String) || (result.is_a?(Hyperstack::Component::Element) && @buffer.empty?) + elsif pushable?(result) @buffer << result end - raise_render_error(result) if is_outer_scope && @buffer != [result] + buffer_integrity_error if is_outer_scope && @buffer != [result] + end + + def dummy_value?(result) + result.respond_to?(:loading?) && result.loading? + end + + def pushable?(result) + # if result is an Element, and buffer is empty then push the Element on, otherwise assume + # it has been pushed on already, and the integrity check will confirm + return @buffer.empty? if result.is_a?(Hyperstack::Component::Element) + + # check for a common error of saying (for example) DIV (without parens) + # which returns the DIV component class instead of a rendered DIV + if result.try :hyper_component? + improper_render "Instead the component class #{result} was returned.", + "Did you mean #{result}()?" + end + + # if the buffer is not empty we will only push on strings, and ignore anything else + return result.is_a?(String) unless @buffer.empty? + + # if the buffer IS empty then we can push on anything + true end - # heurestically raise a meaningful error based on the situation - - def raise_render_error(result) - improper_render 'A different element was returned than was generated within the DSL.', - 'Possibly improper use of Element#delete.' if @buffer.count == 1 - improper_render "Instead #{@buffer.count} elements were generated.", - 'Do you want to wrap your elements in a div?' if @buffer.count > 1 - improper_render "Instead the component #{result} was returned.", - "Did you mean #{result}()?" if result.try :hyper_component? - improper_render "Instead the #{result.class} #{result} was returned.", - 'You may need to convert this to a string.' + def buffer_integrity_error + if @buffer.count == 1 + improper_render "A different element was returned than was generated within the DSL.", + "Possibly improper use of Element#delete." + else + improper_render "Instead #{@buffer.count} elements were generated.", + "Do you want to wrap your elements in a div?" + end end def improper_render(message, solution) @@ -143,7 +174,7 @@ def improper_render(message, solution) end class Object - [:span, :td, :th].each do |tag| + %i[span td th].each do |tag| define_method(tag) do |*args, &block| args.unshift(tag) # legacy hyperloop allowed tags to be lower case as well so if self is a component @@ -155,21 +186,23 @@ class Object # in the component. # If we fully deprecate lowercase tags, then this next line can go... return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component? + Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s } end end - def para(*args, &block) args.unshift(:p) # see above comment return send(*args, &block) if respond_to?(:hyper_component?) && hyper_component? + Hyperstack::Internal::Component::RenderingContext.render(*args) { to_s } end def br # see above comment return send(:br) if respond_to?(:hyper_component?) && hyper_component? + Hyperstack::Internal::Component::RenderingContext.render(:span) do Hyperstack::Internal::Component::RenderingContext.render(to_s) Hyperstack::Internal::Component::RenderingContext.render(:br) diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index 836501a07..a23db83b3 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -237,6 +237,26 @@ class Foo < Hyperloop::Component expect(page).to have_content("paramchildparamchild") end + it "will convert only the final value to a string if the buffer is empty" do + mount 'Foo' do + class Foo < Hyperloop::Component + render { {'foo' => 'bar'} } + end + end + expect(page).to have_content("#{{'foo' => 'bar'}}") + end + + it "will convert only the final value to a string if the buffer is empty" do + # note that the spec 'can create an element without buffering' effectively + # checks other cases where the return value is elements have been rendered to the buffer + mount 'Foo' do + class Foo < Hyperloop::Component + render { DIV { SPAN { 'foo-' }; 'bar' } } + end + end + expect(page).to have_content("foo-bar") + end + it 'can receive and render a component class' do mount 'Baz' do class Bar < Hyperloop::Component @@ -505,15 +525,6 @@ class Foo end describe 'Render Error Handling' do - it "will generate a message if render returns something other than an Element or a String" do - mount 'Foo' do - class Foo < Hyperloop::Component - render { Hash.new } - end - end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/You may need to convert this to a string./) - end it "will generate a message if render returns a Component class" do mount 'Foo' do class Foo < Hyperloop::Component diff --git a/ruby/hyper-model/spec/test_app/db/schema.rb b/ruby/hyper-model/spec/test_app/db/schema.rb index 23819ff59..82639473f 100644 --- a/ruby/hyper-model/spec/test_app/db/schema.rb +++ b/ruby/hyper-model/spec/test_app/db/schema.rb @@ -46,21 +46,6 @@ t.index ["todo_id"], name: "index_comments_on_todo_id" end - create_table "default_tests", force: :cascade do |t| - t.string "string", default: "I'm a string!" - t.date "date", default: "2021-02-27" - t.datetime "datetime", default: "2021-02-27 22:45:41" - t.integer "integer_from_string", default: 99 - t.integer "integer_from_int", default: 98 - t.float "float_from_string", default: 0.02 - t.float "float_from_float", default: 0.01 - t.boolean "boolean_from_falsy_string", default: false - t.boolean "boolean_from_truthy_string", default: true - t.boolean "boolean_from_falsy_value", default: false - t.json "json", default: {"kind"=>"json"} - t.jsonb "jsonb", default: {"kind"=>"jsonb"} - end - create_table "hyperstack_connections", force: :cascade do |t| t.string "channel" t.string "session" @@ -111,23 +96,6 @@ t.index ["owner_id"], name: "index_todos_on_owner_id" end - create_table "type_tests", force: :cascade do |t| - t.binary "binary" - t.boolean "boolean" - t.date "date" - t.datetime "datetime" - t.decimal "decimal", precision: 5, scale: 2 - t.float "float" - t.integer "integer" - t.bigint "bigint" - t.string "string" - t.text "text" - t.time "time" - t.datetime "timestamp" - t.json "json" - t.jsonb "jsonb" - end - create_table "users", force: :cascade do |t| t.string "role" t.bigint "manager_id" From e2c2456686236c341defb8ed1d7e970c543c8859 Mon Sep 17 00:00:00 2001 From: R Mitchell VanDuyn Date: Tue, 9 Mar 2021 20:05:10 -0500 Subject: [PATCH 185/307] closes #158 --- .../internal/component/react_wrapper.rb | 2 +- .../internal/component/rendering_context.rb | 104 +++++++++--------- .../lib/hyperstack/internal/component/tags.rb | 8 ++ .../spec/active_support_spec.rb | 2 +- .../spec/client_features/component_spec.rb | 58 ++++++---- .../spec/client_features/dsl_spec.rb | 9 ++ 6 files changed, 109 insertions(+), 74 deletions(-) diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb index ed3026957..768b10273 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb @@ -19,7 +19,7 @@ class ReactWrapper @@component_classes = {} def self.stateless?(ncc) - `typeof #{ncc} === 'function' && !(#{ncc}.prototype && #{ncc}.prototype.isReactComponent)` + `typeof #{ncc} === 'symbol' || (typeof #{ncc} === 'function' && !(#{ncc}.prototype && #{ncc}.prototype.isReactComponent))` end def self.import_native_component(opal_class, native_class) diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb index f6dba2817..9f0125c79 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rendering_context.rb @@ -28,18 +28,27 @@ def render(name, *args, &block) element = build do saved_waiting_on_resources = nil #waiting_on_resources what was the purpose of this its used below to or in with the current elements waiting_for_resources self.waiting_on_resources = nil - run_child_block(name.nil?, &block) + run_child_block(&block) if name buffer = @buffer.dup ReactWrapper.create_element(name, *args) { buffer }.tap do |element| element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) } element.waiting_on_resources ||= waiting_on_resources if buffer.last.is_a?(String) end - elsif @buffer.last.is_a? Hyperstack::Component::Element - @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources } else - buffer_s = @buffer.last.to_s - RenderingContext.render(:span) { buffer_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources } + buffer = @buffer.collect do |item| + if item.is_a? Hyperstack::Component::Element + item.waiting_on_resources ||= saved_waiting_on_resources + item + else + RenderingContext.render(:span) { item.to_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources } + end + end + if buffer.length > 1 + buffer + else + buffer.first + end end end elsif name.is_a? Hyperstack::Component::Element @@ -65,6 +74,7 @@ def build def delete(element) @buffer.delete(element) + @last_deleted = element element end alias as_node delete @@ -86,28 +96,23 @@ def remove_nodes_from_args(args) end if args[0] && args[0].is_a?(Hash) end - # run_child_block gathers the element(s) generated by a child block. + # run_child_block yields to the child rendering block which will put any + # elements to be rendered into the current rendering buffer. + # # for example when rendering this div: div { "hello".span; "goodby".span } # two child Elements will be generated. # - # the final value of the block should either be - # 1 a hyper model dummy value that is being loaded, - # 2 a string (or if the buffer is empty any value to which to_s can be applied) - # 3 an Element that is NOT yet pushed on the rendering buffer - # 4 or the last Element pushed on the buffer + # However the block itself will return a value, which in some cases should + # also added to the buffer: # - # in case 1 we render a span wrapping the dummy value - # in case 2 we automatically push the string onto the buffer - # in case 3 we also push the Element onto the buffer IF the buffer is empty - # case 4 requires no special processing + # If the final value of the block is a # - # Once we have taken care of these special cases we do a check IF we are in an - # outer rendering scope. In this case react only allows us to generate 1 Element - # so we insure that is the case, and also check to make sure that element in the buffer - # is the element returned - # - # Note that the reason we only allow Strings to be automatically pushed is to avoid - # confusing results in situations like this: + # a hyper model dummy value that is being loaded, then wrap it in a span and add it to the buffer + # a string (or if the buffer is empty any value), then add it to the buffer + # an Element, then add it on the buffer unless it has been just deleted + # # + # Note that the reason we don't always allow Strings to be automatically pushed is + # to avoid confusing results in situations like this: # DIV { collection.each { |item| SPAN { item } } } # If we accepted any object to be rendered this would generate: # DIV { SPAN { collection[0] } SPAN { collection[n] } collection.to_s } @@ -115,57 +120,50 @@ def remove_nodes_from_args(args) # to the end of the expression, to force it to be added to the output buffer. # # However if the buffer is empty then it makes sense to automatically apply the `.to_s` - # to the value, and push it on the buffer + # to the value, and push it on the buffer, unless it is a falsy value or an array - def run_child_block(is_outer_scope) + def run_child_block result = yield + check_for_component_return(result) if dummy_value?(result) # hyper-mesh DummyValues must # be converted to spans INSIDE the parent, otherwise the waiting_on_resources # flag will get set in the wrong context RenderingContext.render(:span) { result.to_s } - elsif pushable?(result) - @buffer << result + elsif result.is_a?(Hyperstack::Component::Element) + @buffer << result if @buffer.empty? unless @last_deleted == result + elsif pushable_string?(result) + @buffer << result.to_s end - buffer_integrity_error if is_outer_scope && @buffer != [result] + @last_deleted = nil + end + + def check_for_component_return(result) + # check for a common error of saying (for example) DIV (without parens) + # which returns the DIV component class instead of a rendered DIV + return unless result.try :hyper_component? + + Hyperstack::Component::IsomorphicHelpers.log( + "a component's render method returned the component class #{result}, did you mean to say #{result}()", + :warning + ) end def dummy_value?(result) result.respond_to?(:loading?) && result.loading? end - def pushable?(result) - # if result is an Element, and buffer is empty then push the Element on, otherwise assume - # it has been pushed on already, and the integrity check will confirm - return @buffer.empty? if result.is_a?(Hyperstack::Component::Element) - - # check for a common error of saying (for example) DIV (without parens) - # which returns the DIV component class instead of a rendered DIV - if result.try :hyper_component? - improper_render "Instead the component class #{result} was returned.", - "Did you mean #{result}()?" - end - + def pushable_string?(result) # if the buffer is not empty we will only push on strings, and ignore anything else return result.is_a?(String) unless @buffer.empty? - # if the buffer IS empty then we can push on anything - true - end - - def buffer_integrity_error - if @buffer.count == 1 - improper_render "A different element was returned than was generated within the DSL.", - "Possibly improper use of Element#delete." - else - improper_render "Instead #{@buffer.count} elements were generated.", - "Do you want to wrap your elements in a div?" - end + # if the buffer IS empty then we can push on anything except we avoid nil, false and arrays + # as these are almost never what you want to render, and if you do there are mechanisms + # to render them explicitly + result && result.respond_to?(:to_n) && !result.is_a?(Array) end def improper_render(message, solution) - raise "a component's render method must generate and return exactly 1 element or a string.\n"\ - " #{message} #{solution}" end end end diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb index c8e010a08..62314587d 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb @@ -32,6 +32,14 @@ module Tags const_set tag.upcase, tag end + const_set "FRAGMENT", ( + Class.new do + include Hyperstack::Component + render {} + Hyperstack::Internal::Component::ReactWrapper.import_native_component(self, `React.Fragment`) + end + ) + # this is used for haml style (i.e. DIV.foo.bar) class tags which is deprecated def self.html_tag_class_for(tag) downcased_tag = tag.downcase diff --git a/ruby/hyper-component/spec/active_support_spec.rb b/ruby/hyper-component/spec/active_support_spec.rb index 04ca6f3ec..8d0546730 100644 --- a/ruby/hyper-component/spec/active_support_spec.rb +++ b/ruby/hyper-component/spec/active_support_spec.rb @@ -51,7 +51,7 @@ def numbers expect_evaluate_ruby do odd_numbers = numbers.extract! { |number| number.odd? } end.to eq [1, 3, 5, 7, 9] - expect_evaluate_ruby do + expect_evaluate_ruby do numbers.extract! { |number| number.odd? } numbers end.to eq [0, 2, 4, 6, 8] diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index a23db83b3..17d51a30d 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -208,6 +208,44 @@ class << self expect_evaluate_ruby('Foo.instance == Foo.instance.force_update!').to be_truthy end + it "can generate multiple elements on outer render using FRAGMENT" do + mount 'Foo' do + class Foo < Hyperloop::Component + render(FRAGMENT) do + UL do + SomeLIs() + LI { "the end" } + end + "random string at the end" + end + end + class SomeLIs < Hyperloop::Component + render(FRAGMENT) { LI { "hello" }; LI { "goodby" } } + end + end + expect(page.find('ul').all('li').collect(&:text)).to eq(['hello', 'goodby', 'the end']) + expect(page.find('div').text).to end_with("random string at the end") + end + + it "can generate multiple elements on outer render by returning arrays" do + mount 'Foo' do + class Foo < Hyperloop::Component + render do + UL(key: 1) do + SomeLIs() + LI(key: 3) { "the end" } + end + "random string at the end".span(key: 2) + end + end + class SomeLIs < Hyperloop::Component + render { LI(key: 1) { "hello" }; LI(key: 2) { "goodby" } } + end + end + expect(page.find('ul').all('li').collect(&:text)).to eq(['hello', 'goodby', 'the end']) + expect(page.find('div').text).to end_with("random string at the end") + end + it 'can buffer an element' do mount 'Foo' do class Bar < Hyperloop::Component @@ -532,25 +570,7 @@ class Foo < Hyperloop::Component end end expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/Did you mean Foo()/) - end - it "will generate a message if more than 1 element is generated" do - mount 'Foo' do - class Foo < Hyperloop::Component - render { "hello".span; "goodby".span } - end - end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/Do you want to wrap your elements in a div?/) - end - it "will generate a message if the element generated is not the element returned" do - mount 'Foo' do - class Foo < Hyperloop::Component - render { "hello".span; "goodby".span.delete } - end - end - expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")) - .to match(/Possibly improper use of Element#delete./) + .to match(/did you mean to say Foo()/) end end diff --git a/ruby/hyper-component/spec/client_features/dsl_spec.rb b/ruby/hyper-component/spec/client_features/dsl_spec.rb index 16c996d21..50836cca7 100644 --- a/ruby/hyper-component/spec/client_features/dsl_spec.rb +++ b/ruby/hyper-component/spec/client_features/dsl_spec.rb @@ -250,6 +250,15 @@ class Foo expect(page.body[-80..-19]).to include('Hyperstack::Component::Element') end + it "the delete method (alias as_node) removes the node from the render buffer" do + mount 'Foo' do + class Foo < Hyperloop::Component + render { "hello".span; "goodby".span.delete } + end + end + expect(find('span').text).to eq('hello') + end + it "has a dom_node method" do mount 'Foo' do class Foo From eff57d8ce3126eddaf4791ff8d074b73ded490e4 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 10 Mar 2021 14:39:43 -0500 Subject: [PATCH 186/307] closes #373 --- .../lib/hyperstack/component.rb | 31 +++++++-- .../internal/component/react_wrapper.rb | 10 +-- .../spec/client_features/component_spec.rb | 2 +- .../spec/deprecation_notices/render.rb | 18 ------ .../spec/deprecation_notices/render_spec.rb | 64 +++++++++++++++++++ .../batch3/has_and_belongs_to_many_spec.rb | 2 +- .../lib/hyperstack/internal/callbacks.rb | 2 +- 7 files changed, 98 insertions(+), 31 deletions(-) delete mode 100644 ruby/hyper-component/spec/deprecation_notices/render.rb create mode 100644 ruby/hyper-component/spec/deprecation_notices/render_spec.rb diff --git a/ruby/hyper-component/lib/hyperstack/component.rb b/ruby/hyper-component/lib/hyperstack/component.rb index 40eb43879..6eb000e83 100644 --- a/ruby/hyper-component/lib/hyperstack/component.rb +++ b/ruby/hyper-component/lib/hyperstack/component.rb @@ -21,13 +21,22 @@ def self.included(base) class_attribute :initial_state define_callback :before_mount define_callback :after_mount - define_callback :before_new_params - define_callback :before_update + define_callback :before_new_params do |klass| + klass.deprecation_warning "`before_new_params` has been deprecated. "\ + "The base method componentWillReceiveProps is deprecated in React without replacement" + end + define_callback(:before_update) do |klass, *args, &block| + [*args, *block].detect do |method| + method = klass.instance_method(method) unless method.is_a?(Proc) + next if method.arity.zero? + + klass.deprecation_warning "In the future before_update callbacks will not receive any parameters." + end + end define_callback :after_update define_callback :__hyperstack_component_after_render_hook define_callback :__hyperstack_component_rescue_hook - #define_callback :before_unmount defined already by Async module - define_callback(:after_error) { Hyperstack::Internal::Component::ReactWrapper.add_after_error_hook(base) } + define_callback(:after_error) { |klass| Hyperstack::Internal::Component::ReactWrapper.add_after_error_hook(klass) } end base.extend(Hyperstack::Internal::Component::ClassMethods) unless `Opal.__hyperstack_component_original_defn` @@ -168,10 +177,22 @@ def __hyperstack_component_run_post_render_hooks(element) run_callback(:__hyperstack_component_after_render_hook, element) { |*args| args }.first end + def _run_before_render_callbacks + # eventually add before_update if @__component_mounted + # but that will not perfectly match the current React behavior. + # However that behavior is deprecated, and so once we have + # given a chance for the code to be updated we can switch this over + # and switch the deprecation notice to an error. + component_will_mount unless @__component_mounted + @__component_mounted = true + end + + def _render_wrapper + _run_before_render_callbacks observing(rendering: true) do element = Hyperstack::Internal::Component::RenderingContext.render(nil) do - render || '' + render || "" end @__hyperstack_component_waiting_on_resources = element.waiting_on_resources if element.respond_to? :waiting_on_resources diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb index 768b10273..4a8c30612 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb @@ -66,9 +66,9 @@ def self.add_after_error_hook_to_native(native_comp) def self.create_native_react_class(type) raise "createReactClass is undefined. Add the 'react-create-class' npm module, and import it as 'createReactClass'" if `typeof(createReactClass)=='undefined'` raise "Provided class should define `render` method" if !(type.method_defined? :render) - render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render + old_school = !type.method_defined?(:_render_wrapper) + render_fn = old_school ? :render : :_render_wrapper # this was hashing type.to_s, not sure why but .to_s does not work as it Foo::Bar::View.to_s just returns "View" - @@component_classes[type] ||= begin comp = %x{ createReactClass({ @@ -88,7 +88,7 @@ def self.create_native_react_class(type) return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`}; }, propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`}, - componentWillMount: function() { + componentWillMount: old_school && function() { if (#{type.method_defined? :component_will_mount}) { this.__opalInstanceSyncSetState = true; this.__opalInstance.$component_will_mount(); @@ -102,7 +102,7 @@ def self.create_native_react_class(type) this.__opalInstance.$component_did_mount(); } }, - componentWillReceiveProps: function(next_props) { + UNSAFE_componentWillReceiveProps: function(next_props) { if (#{type.method_defined? :component_will_receive_props}) { this.__opalInstanceSyncSetState = true; this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props)); @@ -115,7 +115,7 @@ def self.create_native_react_class(type) return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state)); } else { return true; } }, - componentWillUpdate: function(next_props, next_state) { + UNSAFE_componentWillUpdate: function(next_props, next_state) { if (#{type.method_defined? :component_will_update}) { this.__opalInstanceSyncSetState = false; this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state)); diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index 17d51a30d..251d806df 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -122,7 +122,7 @@ class Foo mount 'Foo' do class Foo include Hyperstack::Component - before_mount { @count = 0} + before_mount { @count = 0 } after_render do @count += 1 after(0) { force_update! } if @count <= 2 diff --git a/ruby/hyper-component/spec/deprecation_notices/render.rb b/ruby/hyper-component/spec/deprecation_notices/render.rb deleted file mode 100644 index de0cc912a..000000000 --- a/ruby/hyper-component/spec/deprecation_notices/render.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe 'Deprecation Notices', js: true do - - it "using `defx render` will give a deprecation notice, but still allow render to work" do - mount "TestComp" do - class TestComp < HyperComponent - def render - 'hello' - end - end - end - expect(page).to have_content('hello') - expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( - ["Warning: Deprecated feature used in TestComp. Do not directly define the render method. Use the render macro instead."] - ) - end -end diff --git a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb new file mode 100644 index 000000000..312883501 --- /dev/null +++ b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe 'Deprecation Notices', js: true do + + it "using `def render` will give a deprecation notice, but still allow render to work" do + mount "TestComp" do + class TestComp < HyperComponent + def render + 'hello' + end + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. Do not directly define the render method. Use the render macro instead."] + ) + end + + it "when using before_new_params" do + mount "TestComp" do + class TestComp < HyperComponent + before_new_params { 'bingo' } + render { 'hello' } + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. `before_new_params` has been deprecated. "\ + "The base method componentWillReceiveProps is deprecated in React without replacement" + ] + ) + end + + context "when params are expected in the before_update callback" do + it "when providing a block" do + mount "TestComp" do + class TestComp < HyperComponent + before_update { |x, y| } + render { 'hello' } + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. In the future before_update callbacks will not receive any parameters."] + ) + end + + it "when providing a method name" do + mount "TestComp" do + class TestComp < HyperComponent + def foo(x, y) + end + before_update :foo + render { 'hello' } + end + end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. In the future before_update callbacks will not receive any parameters."] + ) + end + end + +end diff --git a/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb index f5671dcbe..9667387ab 100644 --- a/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb +++ b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb @@ -88,7 +88,7 @@ class Patient < ActiveRecord::Base expect { Physician.first.patients.count }.on_client_to eq(1) Patient.create(name: 'Spock').physicians << mccoy - expect { Physician.first.patients.count }.on_client_to eq(2) + expect { Physician.first.patients.count }.on_client_to eq(2) # this line fails intermittently with == 3! on_client { Patient.create(name: 'Uhuru') } 2.times do diff --git a/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb b/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb index 4e77b346a..f7062c5df 100644 --- a/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb +++ b/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb @@ -34,7 +34,7 @@ def define_callback(callback_name, &after_define_hook) Hotloader.when_file_updates do send(wrapper_name).delete_if { |item| item.equal? args } end - after_define_hook.call(*args, &block) if after_define_hook + after_define_hook.call(self, *args, &block) if after_define_hook end end From 566a06b51e986812520c795c96b34f40b291b48c Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 11 Mar 2021 02:04:19 -0500 Subject: [PATCH 187/307] closes #375 --- ruby/hyper-model/lib/active_record_base.rb | 10 +++ .../reactive_record/collection.rb | 47 +++++++++----- .../reactive_record/operations.rb | 6 ++ .../lib/reactive_record/broadcast.rb | 5 +- .../spec/batch3/aaa_edge_cases_spec.rb | 65 +++++++++++++++++++ 5 files changed, 117 insertions(+), 16 deletions(-) diff --git a/ruby/hyper-model/lib/active_record_base.rb b/ruby/hyper-model/lib/active_record_base.rb index 373d842cd..85e8b08c0 100644 --- a/ruby/hyper-model/lib/active_record_base.rb +++ b/ruby/hyper-model/lib/active_record_base.rb @@ -321,6 +321,16 @@ def do_not_synchronize? self.class.do_not_synchronize? end + before_create :synchromesh_mark_update_time + before_update :synchromesh_mark_update_time + before_destroy :synchromesh_mark_update_time + + attr_reader :__synchromesh_update_time + + def synchromesh_mark_update_time + @__synchromesh_update_time = Time.now.to_f + end + after_commit :synchromesh_after_create, on: [:create] after_commit :synchromesh_after_change, on: [:update] after_commit :synchromesh_after_destroy, on: [:destroy] diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb index 27f30b903..b0262fbcf 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/collection.rb @@ -141,8 +141,10 @@ class TestModel < ApplicationRecord =end + attr_accessor :broadcast_updated_at def sync_scopes(broadcast) + self.broadcast_updated_at = broadcast.updated_at # record_with_current_values will return nil if data between # the broadcast record and the value on the client is out of sync # not running set_pre_sync_related_records will cause sync scopes @@ -160,6 +162,8 @@ def sync_scopes(broadcast) ) record.backing_record.sync_unscoped_collection! if record.destroyed? || broadcast.new? end + ensure + self.broadcast_updated_at = nil end def apply_to_all_collections(method, record, dont_gather) @@ -353,6 +357,7 @@ def count_state=(val) unless ReactiveRecord::WhileLoading.observed? Hyperstack::Internal::State::Variable.set(self, :collection, collection, true) end + @count_updated_at = ReactiveRecord::Operations::Base.last_response_sent_at @count = val end @@ -462,16 +467,18 @@ def push(item) alias << push def _internal_push(item) - item.itself # force get of at least the id - if collection - self.force_push item - else - unsaved_children << item - update_child(item) - @owner.backing_record.sync_has_many(@association.attribute) if @owner && @association - if !@count.nil? - @count += item.destroyed? ? -1 : 1 - notify_of_change self + insure_sync do + item.itself # force get of at least the id + if collection + self.force_push item + else + unsaved_children << item + update_child(item) + @owner.backing_record.sync_has_many(@association.attribute) if @owner && @association + if !@count.nil? + @count += (item.destroyed? ? -1 : 1) + notify_of_change self + end end end self @@ -588,15 +595,25 @@ def destroy(item) join_record&.destroy end + def insure_sync + if Collection.broadcast_updated_at && @count_updated_at && Collection.broadcast_updated_at < @count_updated_at + reload_from_db + else + yield + end + end + alias delete destroy def delete_internal(item) - if collection - all.delete(item) - elsif !@count.nil? - @count -= 1 + insure_sync do + if collection + all.delete(item) + elsif !@count.nil? + @count -= 1 + end + yield if block_given? # was yield item, but item is not used end - yield if block_given? # was yield item, but item is not used item end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb index 3b1f73c87..65b7072ab 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/operations.rb @@ -12,6 +12,10 @@ class Base < Hyperstack::ControllerOp FORMAT = '0x%x' + class << self + attr_accessor :last_response_sent_at + end + def self.serialize_params(hash) hash['associations'].each do |assoc| assoc['parent_id'] = FORMAT % assoc['parent_id'] @@ -38,6 +42,7 @@ def self.serialize_response(response) response[:saved_models].each do |saved_model| saved_model[0] = FORMAT % saved_model[0] end if response.is_a?(Hash) && response[:saved_models] + response[:sent_at] = Time.now.to_f response end @@ -45,6 +50,7 @@ def self.deserialize_response(response) response[:saved_models].each do |saved_model| saved_model[0] = saved_model[0].to_i(16) end if response.is_a?(Hash) && response[:saved_models] + Base.last_response_sent_at = response.delete(:sent_at) response end end diff --git a/ruby/hyper-model/lib/reactive_record/broadcast.rb b/ruby/hyper-model/lib/reactive_record/broadcast.rb index 944fd7a51..5c554efb1 100644 --- a/ruby/hyper-model/lib/reactive_record/broadcast.rb +++ b/ruby/hyper-model/lib/reactive_record/broadcast.rb @@ -11,7 +11,7 @@ def self.after_commit(operation, model) if !Hyperstack.on_server? && Hyperstack::Connection.root_path send_to_server(operation, data) rescue nil # fails if server no longer running so ignore else - SendPacket.run(data, operation: operation) + SendPacket.run(data, operation: operation, updated_at: model.__synchromesh_update_time) end end rescue ActiveRecord::StatementInvalid => e @@ -47,6 +47,7 @@ class SendPacket < Hyperstack::ServerOp param :record param :operation param :previous_changes + param :updated_at unless RUBY_ENGINE == 'opal' validate do @@ -130,6 +131,7 @@ def to_s # private attr_reader :record + attr_reader :updated_at def self.open_channels @open_channels ||= Set.new @@ -167,6 +169,7 @@ def receive(params) @klass ||= params.klass @record.merge! params.record @previous_changes.merge! params.previous_changes + @updated_at = params.updated_at ReactiveRecord::Base.when_not_saving(klass) do @backing_record = ReactiveRecord::Base.exists?(klass, params.record[klass.primary_key]) diff --git a/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb b/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb index 768adce75..2bca9ba51 100644 --- a/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb +++ b/ruby/hyper-model/spec/batch3/aaa_edge_cases_spec.rb @@ -143,6 +143,71 @@ class TestComponent77 < HyperComponent end.to be_nil end + it "will reload scopes when data arrives too late" do + class ActiveRecord::Base + class << self + def public_columns_hash + @public_columns_hash ||= {} + end + end + end + + class BelongsToModel < ActiveRecord::Base + def self.build_tables + connection.create_table :belongs_to_models, force: true do |t| + t.string :name + t.belongs_to :has_many_model + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + class HasManyModel < ActiveRecord::Base + def self.build_tables + connection.create_table :has_many_models, force: true do |t| + t.string :name + t.timestamps + end + ActiveRecord::Base.public_columns_hash[name] = columns_hash + end + end + + BelongsToModel.build_tables #rescue nil + HasManyModel.build_tables #rescue nil + + isomorphic do + class BelongsToModel < ActiveRecord::Base + belongs_to :has_many_model + end + + class HasManyModel < ActiveRecord::Base + has_many :belongs_to_models + end + end + + class HasManyModel < ActiveRecord::Base + def belongs_to_models + sleep 0.3 if name == "sleepy-time" + super + end + end + + class ActiveRecord::Base + alias orig_synchromesh_after_create synchromesh_after_create + def synchromesh_after_create + sleep 0.4 if try(:name) == "sleepy-time" + orig_synchromesh_after_create + end + end + + has_many1 = HasManyModel.create(name: "has_many1") + 2.times { |i| BelongsToModel.create(name: "belongs_to_#{i}", has_many_model: has_many1) } + expect { HasManyModel.first.belongs_to_models.count }.on_client_to eq(1) + BelongsToModel.create(name: "sleepy-time", has_many_model: has_many1) + expect { Hyperstack::Model.load { HasManyModel.first.belongs_to_models.count } }.on_client_to eq(3) + end + describe 'can use finder methods on scopes' do before(:each) do From 5dcc6814b82f3ef81dde293d40b44f65ca91897d Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 11 Mar 2021 09:23:38 -0500 Subject: [PATCH 188/307] spec was missing Model.load causing race --- ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb index 9667387ab..ad9f315ab 100644 --- a/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb +++ b/ruby/hyper-model/spec/batch3/has_and_belongs_to_many_spec.rb @@ -88,7 +88,7 @@ class Patient < ActiveRecord::Base expect { Physician.first.patients.count }.on_client_to eq(1) Patient.create(name: 'Spock').physicians << mccoy - expect { Physician.first.patients.count }.on_client_to eq(2) # this line fails intermittently with == 3! + expect { Hyperstack::Model.load { Physician.first.patients.count } }.on_client_to eq(2) on_client { Patient.create(name: 'Uhuru') } 2.times do From f86dd19829045d4a60d07cbb657de97070b8b472 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 11 Mar 2021 10:31:04 -0500 Subject: [PATCH 189/307] arity check on for all gems --- .../spec/test_app/config/application.rb | 2 +- ruby/hyper-i18n/spec/test_app/config/application.rb | 1 + ruby/hyper-model/spec/test_app/config/application.rb | 2 +- .../spec/test_app/config/application.rb | 2 +- .../lib/hyperstack/internal/router/helpers.rb | 10 +++++----- ruby/hyper-router/spec/test_app/config/application.rb | 1 + ruby/hyper-spec/spec/test_app/config/application.rb | 2 +- ruby/hyper-state/spec/test_app/config/application.rb | 1 + .../spec/test_app/config/application.rb | 1 + 9 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ruby/hyper-component/spec/test_app/config/application.rb b/ruby/hyper-component/spec/test_app/config/application.rb index b8866a00e..8ce8665a8 100644 --- a/ruby/hyper-component/spec/test_app/config/application.rb +++ b/ruby/hyper-component/spec/test_app/config/application.rb @@ -19,7 +19,7 @@ module TestApp class Application < Rails::Application config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-i18n/spec/test_app/config/application.rb b/ruby/hyper-i18n/spec/test_app/config/application.rb index 9c6e80a28..525527648 100644 --- a/ruby/hyper-i18n/spec/test_app/config/application.rb +++ b/ruby/hyper-i18n/spec/test_app/config/application.rb @@ -6,6 +6,7 @@ Bundler.require(*Rails.groups(assets: %w(development test))) module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. diff --git a/ruby/hyper-model/spec/test_app/config/application.rb b/ruby/hyper-model/spec/test_app/config/application.rb index 864804bf0..c3c7f580a 100644 --- a/ruby/hyper-model/spec/test_app/config/application.rb +++ b/ruby/hyper-model/spec/test_app/config/application.rb @@ -17,7 +17,7 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check_enabled = false + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-operation/spec/test_app/config/application.rb b/ruby/hyper-operation/spec/test_app/config/application.rb index 266fdf6c0..0dcdf77e3 100644 --- a/ruby/hyper-operation/spec/test_app/config/application.rb +++ b/ruby/hyper-operation/spec/test_app/config/application.rb @@ -17,7 +17,7 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb b/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb index b8874955c..c2c1b71d3 100644 --- a/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb +++ b/ruby/hyper-router/lib/hyperstack/internal/router/helpers.rb @@ -31,7 +31,7 @@ def Redirect(to, opts = {}) React::Router::Redirect(opts) end - def format_params(e) + def format_params(e, *) { match: Hyperstack::Router::Match.new(`#{e}.match`), location: Hyperstack::Router::Location.new(`#{e}.location`), @@ -47,16 +47,16 @@ def Route(to, opts = {}, &block) if opts[:mounts] component = opts.delete(:mounts) - opts[:component] = lambda do |e| - route_params = format_params(e) + opts[:component] = lambda do |*e| + route_params = format_params(*e) Hyperstack::Component::ReactAPI.create_element(component, route_params).to_n end end if block - opts[:render] = lambda do |e| - route_params = format_params(e) + opts[:render] = lambda do |*e| + route_params = format_params(*e) yield(*route_params.values).to_n end diff --git a/ruby/hyper-router/spec/test_app/config/application.rb b/ruby/hyper-router/spec/test_app/config/application.rb index 179355aa7..a3ff759e6 100644 --- a/ruby/hyper-router/spec/test_app/config/application.rb +++ b/ruby/hyper-router/spec/test_app/config/application.rb @@ -14,6 +14,7 @@ module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # config.opal.method_missing = true # config.opal.optimized_operators = true # config.opal.arity_check = false diff --git a/ruby/hyper-spec/spec/test_app/config/application.rb b/ruby/hyper-spec/spec/test_app/config/application.rb index 6c87017c3..732a9aeba 100644 --- a/ruby/hyper-spec/spec/test_app/config/application.rb +++ b/ruby/hyper-spec/spec/test_app/config/application.rb @@ -26,7 +26,7 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = false + config.opal.arity_check = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-state/spec/test_app/config/application.rb b/ruby/hyper-state/spec/test_app/config/application.rb index 3e63522da..43f4c8d39 100644 --- a/ruby/hyper-state/spec/test_app/config/application.rb +++ b/ruby/hyper-state/spec/test_app/config/application.rb @@ -10,6 +10,7 @@ module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. diff --git a/ruby/hyperstack-config/spec/test_app/config/application.rb b/ruby/hyperstack-config/spec/test_app/config/application.rb index 4d3cbf81e..f59d088bc 100644 --- a/ruby/hyperstack-config/spec/test_app/config/application.rb +++ b/ruby/hyperstack-config/spec/test_app/config/application.rb @@ -7,6 +7,7 @@ module TestApp class Application < Rails::Application + config.opal.arity_check_enabled = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. From 5056ff72bb050477ee9d937593cd62dea0ba2923 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 11 Mar 2021 14:27:58 -0500 Subject: [PATCH 190/307] arity fixes --- .../lib/hyperstack/component.rb | 27 ++++++++++++------- .../component/isomorphic_helpers.rb | 1 - .../spec/deprecation_notices/render_spec.rb | 6 +++++ .../hyperstack/components/no_params_please.rb | 8 ++++++ .../spec/test_app/config/application.rb | 2 +- .../active_record/reactive_record/base.rb | 3 +-- .../reactive_record/dummy_value.rb | 4 +-- .../reactive_record/while_loading.rb | 2 +- .../spec/batch4/default_value_spec.rb | 2 +- .../spec/test_app/config/application.rb | 2 +- .../lib/hyper-spec/internal/patches.rb | 4 +-- .../spec/test_app/config/application.rb | 2 +- 12 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb diff --git a/ruby/hyper-component/lib/hyperstack/component.rb b/ruby/hyper-component/lib/hyperstack/component.rb index 6eb000e83..c86309198 100644 --- a/ruby/hyper-component/lib/hyperstack/component.rb +++ b/ruby/hyper-component/lib/hyperstack/component.rb @@ -25,18 +25,24 @@ def self.included(base) klass.deprecation_warning "`before_new_params` has been deprecated. "\ "The base method componentWillReceiveProps is deprecated in React without replacement" end - define_callback(:before_update) do |klass, *args, &block| - [*args, *block].detect do |method| - method = klass.instance_method(method) unless method.is_a?(Proc) - next if method.arity.zero? - - klass.deprecation_warning "In the future before_update callbacks will not receive any parameters." - end - end + define_callback(:__hyperstack_deprecated_before_update) + define_callback(:__hyperstack_new_style_before_update) define_callback :after_update define_callback :__hyperstack_component_after_render_hook define_callback :__hyperstack_component_rescue_hook define_callback(:after_error) { |klass| Hyperstack::Internal::Component::ReactWrapper.add_after_error_hook(klass) } + + define_singleton_method(:before_update) do |*methods, &block| + methods << block if block + methods.each do |method| + if (method.is_a?(Proc) ? method : instance_method(method)).arity.zero? + __hyperstack_new_style_before_update method + else + deprecation_warning "In the future before_update callbacks will not receive any parameters." + __hyperstack_deprecated_before_update method + end + end + end end base.extend(Hyperstack::Internal::Component::ClassMethods) unless `Opal.__hyperstack_component_original_defn` @@ -116,7 +122,10 @@ def component_will_receive_props(next_props) end def component_will_update(next_props, next_state) - observing { run_callback(:before_update, next_props, next_state) } + observing do + run_callback(:__hyperstack_deprecated_before_update, next_props, next_state) + run_callback(:__hyperstack_new_style_before_update) + end if @__hyperstack_component_receiving_props @__hyperstack_component_params_wrapper.reload(next_props) end diff --git a/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb b/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb index b622d1be1..38e84655e 100644 --- a/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb +++ b/ruby/hyper-component/lib/hyperstack/component/isomorphic_helpers.rb @@ -145,7 +145,6 @@ def eval(js) def send_to_opal(method_name, *args) return unless @ctx - args = [1] if args.length == 0 Hyperstack::Internal::Component::Rails::ComponentLoader.new(@ctx).load! method_args = args.collect do |arg| quarg = "#{arg}".tr('"', "'") diff --git a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb index 312883501..980bd0f0c 100644 --- a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb +++ b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb @@ -32,6 +32,12 @@ class TestComp < HyperComponent end context "when params are expected in the before_update callback" do + + it "no errors if no params" do + mount "NoParamsPlease" + expect(page).to have_content('hello') + end + it "when providing a block" do mount "TestComp" do class TestComp < HyperComponent diff --git a/ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb b/ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb new file mode 100644 index 000000000..8f8a724ad --- /dev/null +++ b/ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb @@ -0,0 +1,8 @@ +class NoParamsPlease < HyperComponent + def no_params_please + @message = "hello" + end + after_mount { mutate @message = "goodby" } + before_update :no_params_please + render { @message } +end diff --git a/ruby/hyper-component/spec/test_app/config/application.rb b/ruby/hyper-component/spec/test_app/config/application.rb index 8ce8665a8..383ddfaec 100644 --- a/ruby/hyper-component/spec/test_app/config/application.rb +++ b/ruby/hyper-component/spec/test_app/config/application.rb @@ -19,7 +19,7 @@ module TestApp class Application < Rails::Application config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = true + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb index b231103dc..4cf628f3c 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/base.rb @@ -311,7 +311,7 @@ def saving! @saving = true end - def errors!(hash, saving) + def errors!(hash, saving = false) @errors_at_last_sync = hash if saving notify_waiting_for_save errors.clear && return unless hash @@ -324,7 +324,6 @@ def errors!(hash, saving) end def revert_errors! - puts "#{inspect}.revert_errors! @errors_at_last_sync: #{@errors_at_last_sync}" errors!(@errors_at_last_sync) end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb index cce04f31f..b56d34812 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/dummy_value.rb @@ -227,10 +227,10 @@ def acts_as_string? # to convert it to a string, for rendering # advantage over a try(:method) is, that it doesnt raise und thus is faster # which is important during render - def respond_to?(method) + def respond_to?(method, all = false) return true if method == :acts_as_string? return true if %i[inspect to_date to_f to_i to_numeric to_number to_s to_time].include? method - return @object.respond_to?(method) if @object + return @object.respond_to?(method, all) if @object false end diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb index 96c6a3dca..76d54fcf5 100644 --- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb +++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/while_loading.rb @@ -353,7 +353,7 @@ def add_style_sheet end - def after_mount_and_update + def after_mount_and_update(*) @waiting_on_resources = @Loading node = dom_node %x{ diff --git a/ruby/hyper-model/spec/batch4/default_value_spec.rb b/ruby/hyper-model/spec/batch4/default_value_spec.rb index 635d76810..1933ba2b1 100644 --- a/ruby/hyper-model/spec/batch4/default_value_spec.rb +++ b/ruby/hyper-model/spec/batch4/default_value_spec.rb @@ -210,7 +210,7 @@ class InputTester < HyperComponent expect(find('#controlled-select').value).to eq('another value') expect(find('#controlled-textarea').value).to eq('another value') - find('#uncontrolled-input').set 'I was set by the user' + find('#uncontrolled-input').set 'I was set by the user', clear: :backspace expect(find('#uncontrolled-input').value).to eq('I was set by the user') find('#uncontrolled-checkbox').set(false) diff --git a/ruby/hyper-operation/spec/test_app/config/application.rb b/ruby/hyper-operation/spec/test_app/config/application.rb index 0dcdf77e3..424c42ada 100644 --- a/ruby/hyper-operation/spec/test_app/config/application.rb +++ b/ruby/hyper-operation/spec/test_app/config/application.rb @@ -17,7 +17,7 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = true + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb index eaebd2b90..d5f13c3f6 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/patches.rb @@ -1,8 +1,8 @@ module Opal # strips off stuff that confuses things when transmitting to the client # and prints offending code if it can't be compiled - def self.hyperspec_compile(str) - compile(str).gsub("// Prepare super implicit arguments\n", '') + def self.hyperspec_compile(str, opts = {}) + compile(str, opts).gsub("// Prepare super implicit arguments\n", '') .delete("\n").gsub('(Opal);', '(Opal)') # rubocop:disable Lint/RescueException # we are going to reraise it anyway, so its fine to catch EVERYTHING! diff --git a/ruby/hyper-spec/spec/test_app/config/application.rb b/ruby/hyper-spec/spec/test_app/config/application.rb index 732a9aeba..87e81cf44 100644 --- a/ruby/hyper-spec/spec/test_app/config/application.rb +++ b/ruby/hyper-spec/spec/test_app/config/application.rb @@ -26,7 +26,7 @@ class Application < Rails::Application config.assets.paths << ::Rails.root.join('app', 'models').to_s config.opal.method_missing = true config.opal.optimized_operators = true - config.opal.arity_check = true + config.opal.arity_check_enabled = true config.opal.const_missing = true config.opal.dynamic_require_severity = :ignore config.opal.enable_specs = true From da69ea7968c7bb44d1c8b6a007f576314317f782 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 11 Mar 2021 16:27:11 -0500 Subject: [PATCH 191/307] fixed a few more arity issues --- .../collection_aggregate_methods_spec.rb | 70 +++++++++---------- .../lib/hyper-operation/railway/run.rb | 5 +- .../spec/hyper-operation/server_op_spec.rb | 4 +- ruby/hyper-spec/lib/hyper-spec/helpers.rb | 2 +- .../hyper-spec/internal/client_execution.rb | 4 +- .../hyper-spec/internal/component_mount.rb | 2 +- ruby/hyper-spec/spec/hyper_spec.rb | 7 ++ 7 files changed, 49 insertions(+), 45 deletions(-) diff --git a/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb b/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb index dfc08b7d6..9cabaf5d2 100644 --- a/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb +++ b/ruby/hyper-model/spec/batch2/collection_aggregate_methods_spec.rb @@ -36,57 +36,51 @@ it "will not retrieve the entire collection when using #{method}" do FactoryBot.create(:test_model) - expect_promise( - <<~RUBY - Hyperstack::Model - .load { TestModel.#{method} } - .then do |val| - if TestModel.all.instance_variable_get('@collection') - "unnecessary fetch of all" - else - val - end + expect do + Hyperstack::Model + .load { TestModel.send(method) } + .then do |val| + if TestModel.all.instance_variable_get('@collection') + "unnecessary fetch of all" + else + val end - RUBY - ).to eq(TestModel.all.send(method)) + end + end.on_client_to eq(TestModel.all.send(method)) end end %i[any? none?].each do |method| - it 'will retrieve the entire collection when using any? if an arg is passed in' do + it "will retrieve the entire collection when using #{method} if an arg is passed in" do FactoryBot.create(:test_model) - expect_promise( - <<~RUBY - Hyperstack::Model.load do - TestModel.#{method}(TestModel) - end.then do |val| - if TestModel.all.instance_variable_get('@collection') - 'necessary fetch of all' - else - val - end + expect do + Hyperstack::Model.load do + TestModel.send(method, TestModel) + end.then do |val| + if TestModel.all.instance_variable_get('@collection') + 'necessary fetch of all' + else + val end - RUBY - ).to eq('necessary fetch of all') - end + end + end.on_client_to eq('necessary fetch of all') + end unless Opal::VERSION.split('.')[0..1] == ['0', '11'] # opal 0.11 didn't support a value passed to any it 'will retrieve the entire collection when using any? if a block is passed in' do FactoryBot.create(:test_model) - expect_promise( - <<~RUBY - Hyperstack::Model.load do - TestModel.#{method} { |test_model| test_model } - end.then do |val| - if TestModel.all.instance_variable_get('@collection') - 'necessary fetch of all' - else - val - end + expect do + Hyperstack::Model.load do + TestModel.send(method) { |test_model| test_model } + end.then do |val| + if TestModel.all.instance_variable_get('@collection') + 'necessary fetch of all' + else + val end - RUBY - ).to eq('necessary fetch of all') + end + end.on_client_to eq('necessary fetch of all') end end end diff --git a/ruby/hyper-operation/lib/hyper-operation/railway/run.rb b/ruby/hyper-operation/lib/hyper-operation/railway/run.rb index c724843e8..69a7ca445 100644 --- a/ruby/hyper-operation/lib/hyper-operation/railway/run.rb +++ b/ruby/hyper-operation/lib/hyper-operation/railway/run.rb @@ -4,10 +4,13 @@ class Operation class Exit < StandardError attr_reader :state attr_reader :result - def initialize(state, result) + def initialize(state, result = nil) @state = state @result = result end + def to_s + @state + end end class Railway diff --git a/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb b/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb index a8e1d24cc..7d8371952 100644 --- a/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/server_op_spec.rb @@ -89,11 +89,11 @@ def fact(x) class ServerFacts < Hyperstack::ServerOp step { abort! } end - expect_promise do + expect do ServerFacts.run(n: 5).fail do |exception| Promise.new.resolve(exception.inspect) end - end.to eq('#') + end.on_client_to eq("#") expect(response_spy).to have_received(:status=).with(500) end diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index 8fc2b5185..579362822 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -164,7 +164,7 @@ def insert_html(str) end def ppr(str) - js = Opal.hyperspec_compile(str) + js = opal_compile(str) execute_script("console.log(#{js})") end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb index 51f65c52f..e5a4fb8fc 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb @@ -84,8 +84,8 @@ def the_node_you_are_looking_for?(node) end - def opal_compile(str, *) - Opal.hyperspec_compile(str) + def opal_compile(str) + Opal.hyperspec_compile(str, arity_check: client_options[:arity_check]) end end end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb index 380121be3..f91b74a10 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb @@ -44,7 +44,7 @@ def self.add_class(class_name, styles={}) #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} RUBY @_hyperspec_private_client_code = nil - opts[:code] = Opal.hyperspec_compile(block_with_helpers) + opts[:code] = opal_compile(block_with_helpers) end # rubocop:enable Metrics/MethodLength diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 90cd4e8a7..c0844c350 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -455,6 +455,13 @@ class << self expect { with_var * 2 }.with(with_var: 4).on_client_to eq(8) end end + + it "will use the arity_check option" do + client_option arity_check: true + expect { -> (x, y: 2) { }.parameters }.on_client_to eq [["req", "x"], ["key", "y"]] + client_option arity_check: false + expect { -> (x, y: 2) { }.parameters }.on_client_to eq [] + end end RSpec::Steps.steps "will size_window to", js: true do From 6e5fec4f585e0f936d4af610ddf590927e2f58dd Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 12 Mar 2021 07:49:43 -0500 Subject: [PATCH 192/307] closes #376 --- .../spec/deprecation_notices/render_spec.rb | 11 ++++++++++- .../spec/aaa_run_first/transports_spec.rb | 2 +- ruby/hyper-spec/lib/hyper-spec/helpers.rb | 6 +++++- ruby/hyper-spec/spec/hyper_spec.rb | 4 +++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb index 980bd0f0c..d50163a9a 100644 --- a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb +++ b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb @@ -34,7 +34,16 @@ class TestComp < HyperComponent context "when params are expected in the before_update callback" do it "no errors if no params" do - mount "NoParamsPlease" + mount "TestComp" do + class TestComp < HyperComponent + def no_params_please + @message = "hello" + end + after_mount { mutate @message = "goodby" } + before_update :no_params_please + render { @message } + end + end expect(page).to have_content('hello') end diff --git a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb index 59313fa32..cf2c646be 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb @@ -373,7 +373,7 @@ def connect(*args) expect_evaluate_ruby('Hyperstack.anti_csrf_token').to be_present end - it 'wait for an instance channel to be loaded before connecting' do + it 'wait for an instance channel to be loaded before connecting' do # 2 = action_cable # Make a pretend mini model, and allow it to be accessed by user-123 stub_const "UserModelPolicy", Class.new stub_const "UserModel", Class.new diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index 579362822..5ec0dfc73 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -89,12 +89,16 @@ def isomorphic(&block) # Allows options to the mount method to be specified globally def client_option(opts = {}) - @_hyperspec_private_client_options ||= {} + @_hyperspec_private_client_options ||= { arity_check: default_arity_check } @_hyperspec_private_client_options.merge! opts build_var_inclusion_lists @_hyperspec_private_client_options end + def default_arity_check + Rails&.application&.config&.opal&.arity_check_enabled if defined? Rails + end + alias client_options client_option ## diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index c0844c350..8f4e6e333 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -457,10 +457,12 @@ class << self end it "will use the arity_check option" do - client_option arity_check: true + # We run the specs with arity_check_enabled so this should default to being on expect { -> (x, y: 2) { }.parameters }.on_client_to eq [["req", "x"], ["key", "y"]] client_option arity_check: false expect { -> (x, y: 2) { }.parameters }.on_client_to eq [] + client_option arity_check: true + expect { -> (x, y: 2) { }.parameters }.on_client_to eq [["req", "x"], ["key", "y"]] end end From b625905100aa7aa739c31fbc35a81e9c28ebf46c Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 13 Mar 2021 11:09:42 -0500 Subject: [PATCH 193/307] more arity check fixes --- .../lib/hyperstack/component.rb | 61 +++++++------ .../internal/component/rescue_wrapper.rb | 2 +- .../spec/deprecation_notices/render_spec.rb | 86 +++++++++++-------- .../hyperstack/components/no_params_please.rb | 8 -- .../spec/aaa_run_first/transports_spec.rb | 21 +++-- ruby/hyper-spec/lib/hyper-spec/helpers.rb | 3 +- .../hyper-spec/internal/component_mount.rb | 2 +- .../lib/hyperstack/internal/callbacks.rb | 17 ++-- .../spec/internal/callbacks_spec.rb | 60 ++++++++----- 9 files changed, 148 insertions(+), 112 deletions(-) delete mode 100644 ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb diff --git a/ruby/hyper-component/lib/hyperstack/component.rb b/ruby/hyper-component/lib/hyperstack/component.rb index c86309198..49271dc45 100644 --- a/ruby/hyper-component/lib/hyperstack/component.rb +++ b/ruby/hyper-component/lib/hyperstack/component.rb @@ -19,30 +19,40 @@ def self.included(base) base.include(Hyperstack::Internal::Component::ShouldComponentUpdate) base.class_eval do class_attribute :initial_state - define_callback :before_mount - define_callback :after_mount - define_callback :before_new_params do |klass| - klass.deprecation_warning "`before_new_params` has been deprecated. "\ - "The base method componentWillReceiveProps is deprecated in React without replacement" - end - define_callback(:__hyperstack_deprecated_before_update) - define_callback(:__hyperstack_new_style_before_update) - define_callback :after_update - define_callback :__hyperstack_component_after_render_hook - define_callback :__hyperstack_component_rescue_hook - define_callback(:after_error) { |klass| Hyperstack::Internal::Component::ReactWrapper.add_after_error_hook(klass) } - - define_singleton_method(:before_update) do |*methods, &block| - methods << block if block - methods.each do |method| - if (method.is_a?(Proc) ? method : instance_method(method)).arity.zero? - __hyperstack_new_style_before_update method - else - deprecation_warning "In the future before_update callbacks will not receive any parameters." - __hyperstack_deprecated_before_update method - end + + method_args_deprecation_check = lambda do |name, sself, proc, *args| + if proc.arity.zero? + args = [] + else + deprecation_warning "In the future #{name} callbacks will not receive any parameters." end + sself.instance_exec(*args, &proc) + args end + + define_callback :before_mount, before_call_hook: method_args_deprecation_check + define_callback :after_mount + define_callback( + :before_new_params, + after_define_hook: lambda do |klass| + klass.deprecation_warning "`before_new_params` has been deprecated. The base "\ + "method componentWillReceiveProps is deprecated in React without replacement" + end + ) + define_callback(:before_update, before_call_hook: method_args_deprecation_check) + define_callback :after_update + define_callback( + :__hyperstack_component_after_render_hook, + before_call_hook: ->(_, sself, proc, *args) { [*sself.instance_exec(*args, &proc)] } + ) + define_callback( + :__hyperstack_component_rescue_hook, + before_call_hook: ->(_, sself, proc, *args) { sself.instance_exec(*args, &proc) } + ) + define_callback( + :after_error, + after_define_hook: ->(klass) { Hyperstack::Internal::Component::ReactWrapper.add_after_error_hook(klass) } + ) end base.extend(Hyperstack::Internal::Component::ClassMethods) unless `Opal.__hyperstack_component_original_defn` @@ -122,10 +132,7 @@ def component_will_receive_props(next_props) end def component_will_update(next_props, next_state) - observing do - run_callback(:__hyperstack_deprecated_before_update, next_props, next_state) - run_callback(:__hyperstack_new_style_before_update) - end + observing { run_callback(:before_update, next_props, next_state) } if @__hyperstack_component_receiving_props @__hyperstack_component_params_wrapper.reload(next_props) end @@ -183,7 +190,7 @@ def waiting_on_resources end def __hyperstack_component_run_post_render_hooks(element) - run_callback(:__hyperstack_component_after_render_hook, element) { |*args| args }.first + run_callback(:__hyperstack_component_after_render_hook, element).first end def _run_before_render_callbacks diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb index 429a59780..d3deb43ca 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rescue_wrapper.rb @@ -27,7 +27,7 @@ class << self after_error do |error, info| args = RescueWrapper.after_error_args || [error, info] - found, * = @Child.run_callback(:__hyperstack_component_rescue_hook, found, *args) { |a| a } + found, * = @Child.run_callback(:__hyperstack_component_rescue_hook, found, *args) unless found RescueWrapper.after_error_args = args raise error diff --git a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb index d50163a9a..dc79cf031 100644 --- a/ruby/hyper-component/spec/deprecation_notices/render_spec.rb +++ b/ruby/hyper-component/spec/deprecation_notices/render_spec.rb @@ -23,57 +23,67 @@ class TestComp < HyperComponent render { 'hello' } end end - expect(page).to have_content('hello') + expect(page).to have_content("hello") expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( - ["Warning: Deprecated feature used in TestComp. `before_new_params` has been deprecated. "\ - "The base method componentWillReceiveProps is deprecated in React without replacement" - ] - ) - end + [ + "Warning: Deprecated feature used in TestComp. `before_new_params` has been deprecated. "\ + "The base method componentWillReceiveProps is deprecated in React without replacement" + ] + ) + end - context "when params are expected in the before_update callback" do + %i[mount update].each do |callback_name| + context "when params are expected in the before_#{callback_name} callback" do + + before(:all) do + before_mount { CALLBACK_NAME = "before_#{callback_name}" } + end - it "no errors if no params" do - mount "TestComp" do - class TestComp < HyperComponent - def no_params_please - @message = "hello" + it "no errors if no params" do + mount "TestComp" do + class TestComp < HyperComponent + def no_params_please + @message = "hello" + end + send(CALLBACK_NAME, :no_params_please) + after_mount { mutate @message = "goodby" unless @message } + render { @message } end - after_mount { mutate @message = "goodby" } - before_update :no_params_please - render { @message } end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to be_nil end - expect(page).to have_content('hello') - end - it "when providing a block" do - mount "TestComp" do - class TestComp < HyperComponent - before_update { |x, y| } - render { 'hello' } + it "when providing a block" do + mount "TestComp" do + class TestComp < HyperComponent + send(CALLBACK_NAME) { |x, y| @message = "hello" } + after_mount { mutate @message = "goodby" unless @message } + render { @message } + end end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. In the future before_#{callback_name} callbacks will not receive any parameters."] + ) end - expect(page).to have_content('hello') - expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( - ["Warning: Deprecated feature used in TestComp. In the future before_update callbacks will not receive any parameters."] - ) - end - it "when providing a method name" do - mount "TestComp" do - class TestComp < HyperComponent - def foo(x, y) + it "when providing a method name" do + mount "TestComp" do + class TestComp < HyperComponent + def foo(x, y=nil) # so it works with both before_mount (1 arg) and before_update (2 args) + @message = "hello" + end + send(CALLBACK_NAME, :foo) + after_mount { mutate @message = "goodby" unless @message} + render { @message } end - before_update :foo - render { 'hello' } end + expect(page).to have_content('hello') + expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( + ["Warning: Deprecated feature used in TestComp. In the future before_#{callback_name} callbacks will not receive any parameters."] + ) end - expect(page).to have_content('hello') - expect_evaluate_ruby("Hyperstack.instance_variable_get('@deprecation_messages')").to eq( - ["Warning: Deprecated feature used in TestComp. In the future before_update callbacks will not receive any parameters."] - ) end end - end diff --git a/ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb b/ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb deleted file mode 100644 index 8f8a724ad..000000000 --- a/ruby/hyper-component/spec/test_app/app/hyperstack/components/no_params_please.rb +++ /dev/null @@ -1,8 +0,0 @@ -class NoParamsPlease < HyperComponent - def no_params_please - @message = "hello" - end - after_mount { mutate @message = "goodby" } - before_update :no_params_please - render { @message } -end diff --git a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb index cf2c646be..49e060210 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb @@ -373,6 +373,10 @@ def connect(*args) expect_evaluate_ruby('Hyperstack.anti_csrf_token').to be_present end + # after(:each) do |example| + # binding.pry #if example.exception + # end + it 'wait for an instance channel to be loaded before connecting' do # 2 = action_cable # Make a pretend mini model, and allow it to be accessed by user-123 stub_const "UserModelPolicy", Class.new @@ -399,8 +403,8 @@ def id isomorphic do class CheckIn < Hyperstack::ControllerOp param :id - validate { params.id == '123' } - step { params.id } + validate { puts "validating params.id == '123': #{params.id == '123'}"; params.id == '123' } + step { params.id } # return the id to the client simulating checkin of valid user end end @@ -408,9 +412,11 @@ class CheckIn < Hyperstack::ControllerOp # Stub the user model on the client class UserModel def initialize(id) + puts "UserModel.new(#{id})" @id = id end def id + puts "self.id = #{@id}" @id.to_s end end @@ -419,6 +425,7 @@ def id module Hyperstack module Model def self.load + puts "loading id from server - simulating HyperModel load of id for an object" CheckIn.run(id: yield) end end @@ -433,11 +440,15 @@ def self.load end expect(CheckIn).to receive(:run).and_call_original - evaluate_ruby 'Hyperstack.connect(UserModel.new(123))' + evaluate_ruby { puts "setting up connecting"; Hyperstack.connect(UserModel.new(123)) } # the suite previously redefined connect so we have to call this to initiate # the connection - evaluate_ruby 'Hyperstack.go_ahead_and_connect' - wait(10.seconds).for { Hyperstack::Connection.active }.to eq(['UserModel-123']) + evaluate_ruby { puts "connecting"; Hyperstack.go_ahead_and_connect } + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + wait_for do + sleep 0.25 + Hyperstack::Connection.active.tap { |c| puts "active = [#{c}]"} + end.to eq(['UserModel-123']) end end end diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index 5ec0dfc73..ac881d813 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -74,8 +74,7 @@ def mount(component_name = nil, params = nil, opts = {}, &block) def before_mount(&block) @_hyperspec_private_client_code = - "#{@_hyperspec_private_client_code}"\ - "#{Unparser.unparse Parser::CurrentRuby.parse(block.source).children.last}\n" + "#{@_hyperspec_private_client_code}#{add_opal_block('', block)}" end # Execute the block both on the client and on the server. Useful diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb index f91b74a10..366ea71a7 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/component_mount.rb @@ -41,7 +41,7 @@ def self.add_class(class_name, styles={}) end #{test_dummy} #{@_hyperspec_private_client_code} - #{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last) if block} + #{"#{add_locals('', block)}\n#{Unparser.unparse(Parser::CurrentRuby.parse(block.source).children.last)}" if block} RUBY @_hyperspec_private_client_code = nil opts[:code] = opal_compile(block_with_helpers) diff --git a/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb b/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb index f7062c5df..3b1709c04 100644 --- a/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb +++ b/ruby/hyper-state/lib/hyperstack/internal/callbacks.rb @@ -12,29 +12,30 @@ def self.included(base) def run_callback(name, *args) self.class.callbacks_for(name).flatten.each do |callback| - result = if callback.is_a?(Proc) - instance_exec(*args, &callback) - else - send(callback, *args) - end - args = yield(result) if block_given? + callback = method(callback) unless callback.is_a? Proc + args = self.class.send("_#{name}_before_call_hook", name, self, callback, *args) end args end module ClassMethods - def define_callback(callback_name, &after_define_hook) + def define_callback(callback_name, before_call_hook: nil, after_define_hook: nil) wrapper_name = "_#{callback_name}_callbacks" define_singleton_method(wrapper_name) do Context.set_var(self, "@#{wrapper_name}", force: true) { [] } end + before_call_hook ||= lambda do |_name, sself, proc, *args| + sself.instance_exec(*args, &proc) + args + end + define_singleton_method("_#{callback_name}_before_call_hook", &before_call_hook) define_singleton_method(callback_name) do |*args, &block| args << block if block_given? send(wrapper_name).push args Hotloader.when_file_updates do send(wrapper_name).delete_if { |item| item.equal? args } end - after_define_hook.call(self, *args, &block) if after_define_hook + after_define_hook.call(self) if after_define_hook end end diff --git a/ruby/hyper-state/spec/internal/callbacks_spec.rb b/ruby/hyper-state/spec/internal/callbacks_spec.rb index 21f1dbbd1..04f3d15d6 100644 --- a/ruby/hyper-state/spec/internal/callbacks_spec.rb +++ b/ruby/hyper-state/spec/internal/callbacks_spec.rb @@ -25,48 +25,64 @@ class Foo define_callback :before_dinner before_dinner :wash_hands, :turn_off_laptop - def wash_hands;end - def turn_off_laptop;end + attr_reader :washed_hands + attr_reader :turned_off_laptop + + def wash_hands(*args) + @washed_hands = args + end + def turn_off_laptop(*args) + @turned_off_laptop = args + end end end - expect_evaluate_ruby do - instance = Foo.new - [ instance.respond_to?(:wash_hands), - instance.respond_to?(:turn_off_laptop), - instance.run_callback(:before_dinner, 1, 2, 3) ] - end.to eq([true, true, [1, 2, 3]]) + evaluate_ruby { @instance = Foo.new } + expect { @instance.respond_to?(:wash_hands) }.on_client_to be_truthy + expect { @instance.respond_to?(:turn_off_laptop) }.on_client_to be_truthy + expect { @instance.run_callback(:before_dinner, 1, 2, 3) }.on_client_to eq [1, 2, 3] + expect { @instance.washed_hands }.on_client_to eq [1, 2, 3] + expect { @instance.turned_off_laptop }.on_client_to eq [1, 2, 3] end context 'using Hyperloop::Context.reset!' do - #after(:all) do - # Hyperloop::Context.instance_variable_set(:@context, nil) - #end it 'clears callbacks on Hyperloop::Context.reset!' do on_client do Hyperstack::Context.reset! - class Foo include Hyperstack::Internal::Callbacks define_callback :before_dinner - before_dinner :wash_hands, :turn_off_laptop - def wash_hands;end + attr_reader :washed_hands + attr_reader :turned_off_laptop - def turn_off_laptop;end + def wash_hands(*args) + @washed_hands = args + end + def turn_off_laptop(*args) + @turned_off_laptop = args + end end end - expect_evaluate_ruby do - instance = Foo.new - + evaluate_ruby { @instance = Foo.new } + expect { @instance.run_callback(:before_dinner, 1, 2, 3) }.on_client_to eq [1, 2, 3] + expect { @instance.washed_hands }.on_client_to eq [1, 2, 3] + expect { @instance.turned_off_laptop }.on_client_to eq [1, 2, 3] + evaluate_ruby do + @instance = Foo.new Hyperstack::Context.reset! - + end + expect { @instance.run_callback(:before_dinner, 1, 2, 3) }.on_client_to eq [1, 2, 3] + expect { @instance.washed_hands }.on_client_to be_nil + expect { @instance.turned_off_laptop }.on_client_to be_nil + evaluate_ruby do Foo.class_eval do before_dinner :wash_hands end - - instance.run_callback(:before_dinner, 1, 2, 3) - end.to eq([1, 2, 3]) + end + expect { @instance.run_callback(:before_dinner, 4, 5) }.on_client_to eq [4, 5] + expect { @instance.washed_hands }.on_client_to eq [4, 5] + expect { @instance.turned_off_laptop }.on_client_to be_nil end end From cb7f4a75dd0b36aca260f9a2b2ab68644a338d4f Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 13 Mar 2021 22:20:47 -0500 Subject: [PATCH 194/307] closes more arity issues and closes #378 and closes #379 --- ruby/hyper-operation/lib/hyper-operation/api.rb | 3 ++- .../lib/hyper-operation/railway/dispatcher.rb | 1 - .../spec/hyper-operation/execution_spec.rb | 4 ++-- .../spec/hyper-operation/reset_context_spec.rb | 10 +++++----- .../lib/hyper-spec/internal/client_execution.rb | 2 ++ ruby/hyper-spec/spec/hyper_spec.rb | 8 ++++++++ 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ruby/hyper-operation/lib/hyper-operation/api.rb b/ruby/hyper-operation/lib/hyper-operation/api.rb index 7ba464595..7ba4653cd 100644 --- a/ruby/hyper-operation/lib/hyper-operation/api.rb +++ b/ruby/hyper-operation/lib/hyper-operation/api.rb @@ -47,7 +47,8 @@ def _run(*args) @_railway.process_validations @_railway.run @_railway.dispatch - @_railway.result + # return the result from dispatch in case there is an error + # @_railway.result end end diff --git a/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb b/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb index 8e80c5da2..54b0d6809 100644 --- a/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb +++ b/ruby/hyper-operation/lib/hyper-operation/railway/dispatcher.rb @@ -1,7 +1,6 @@ module Hyperstack class Operation class Railway - def receivers self.class.receivers end diff --git a/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb b/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb index 164aeeed8..867b783b7 100644 --- a/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/execution_spec.rb @@ -381,8 +381,8 @@ class SayHelloOp < Hyperstack::Operation TestOperation.class_eval do param :xxx extend HelloCounter - def say_hello(test = false) - self.class.say_hello(test) + def say_hello + self.class.say_hello end step { say_hello } step :say_hello diff --git a/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb b/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb index add0f085a..3b7094c60 100644 --- a/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb +++ b/ruby/hyper-operation/spec/hyper-operation/reset_context_spec.rb @@ -52,12 +52,12 @@ class Store class << self attr_reader :boot_calls attr_reader :another_receiver_calls - def booted + def booted(*) puts " booted called" @boot_calls ||= 0 @boot_calls += 1 end - def another_receiver + def another_receiver(*) puts " receiver called" @another_receiver_calls ||= 0 @another_receiver_calls += 1 @@ -72,14 +72,14 @@ class Store receives Hyperstack::Application::Boot, :another_receiver end end - evaluate_ruby("puts '>>booting'") # do a separate evaluation to get initial boot completed + evaluate_ruby { puts '>>booting' } # do a separate evaluation to get initial boot completed evaluate_ruby do puts '>>resetting again' Hyperstack::Context.reset!(nil) puts '>>booting again' Hyperstack::Application::Boot.run end - expect_evaluate_ruby('Store.boot_calls').to eq(2) - expect_evaluate_ruby('Store.another_receiver_calls').to eq(1) + expect { Store.boot_calls }.on_client_to eq(2) + expect { Store.another_receiver_calls }.on_client_to eq(1) end end diff --git a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb index e5a4fb8fc..39d3666fb 100644 --- a/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb +++ b/ruby/hyper-spec/lib/hyper-spec/internal/client_execution.rb @@ -25,6 +25,7 @@ def add_promise_execute_and_wait(str, opts) Timeout.timeout(Capybara.default_max_wait_time) do loop do break if page.evaluate_script('!!window.hyper_spec_promise_result') + page.evaluate_script('!!window.hyper_spec_promise_failed && Opal.Opal.$raise(window.hyper_spec_promise_failed)') sleep 0.25 end @@ -37,6 +38,7 @@ def add_promise_wrapper(str) (#{str}).tap do |r| if defined?(Promise) && r.is_a?(Promise) r.then { |args| `window.hyper_spec_promise_result = [args]` } + .fail { |e| `window.hyper_spec_promise_failed = e` } else #after(0) do #puts "setting window.hyper_spec_promise_result = [\#{r}]" diff --git a/ruby/hyper-spec/spec/hyper_spec.rb b/ruby/hyper-spec/spec/hyper_spec.rb index 8f4e6e333..d32900a94 100644 --- a/ruby/hyper-spec/spec/hyper_spec.rb +++ b/ruby/hyper-spec/spec/hyper_spec.rb @@ -137,6 +137,14 @@ def wait(seconds) end.to eq(DELAY) expect(Time.now-start).to be >= DELAY end + + it "will raise an error if a promise is rejected" do + begin + on_client { Promise.new.reject("foo") } + rescue StandardError => e + expect(e.message).to start_with "javascript error: foo\n" + end + end end context 'event and callback handlers' do From f5d379805c2fa436b8bb0de1410c16557a4692a6 Mon Sep 17 00:00:00 2001 From: catmando Date: Sat, 13 Mar 2021 22:51:43 -0500 Subject: [PATCH 195/307] closes #379 --- ruby/hyper-operation/lib/hyper-operation/api.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ruby/hyper-operation/lib/hyper-operation/api.rb b/ruby/hyper-operation/lib/hyper-operation/api.rb index 7ba4653cd..5b11e3d4f 100644 --- a/ruby/hyper-operation/lib/hyper-operation/api.rb +++ b/ruby/hyper-operation/lib/hyper-operation/api.rb @@ -46,9 +46,12 @@ def _run(*args) @_railway.process_params(args) @_railway.process_validations @_railway.run - @_railway.dispatch # return the result from dispatch in case there is an error - # @_railway.result + if (dispatch_result = @_railway.dispatch).rejected? + dispatch_result + else + @_railway.result + end end end From 7c4f5c7f747873792312f41a23f96718233cea30 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 14 Mar 2021 15:52:22 -0400 Subject: [PATCH 196/307] attempting to fix intermittent transport specs --- docs/hyper-spec/03-hyperspec-methods-and-features.md | 8 ++++---- .../hyper-operation/spec/aaa_run_first/transports_spec.rb | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/hyper-spec/03-hyperspec-methods-and-features.md b/docs/hyper-spec/03-hyperspec-methods-and-features.md index 448bd0d57..8fec6d0c6 100644 --- a/docs/hyper-spec/03-hyperspec-methods-and-features.md +++ b/docs/hyper-spec/03-hyperspec-methods-and-features.md @@ -89,7 +89,7 @@ The on_client method takes a block. The ruby code inside the block will be exec end ``` -If the block returns a promise Hyperspec will wait for the promise to be resolved before returning. For example: +If the block returns a promise Hyperspec will wait for the promise to be resolved (or rejected) before returning. For example: ```ruby it 'waits for a promise' do @@ -135,7 +135,7 @@ across blocks executed on the client. For example: end ``` -> Be especially careful of this when using the [`no_reset`](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. +> Be especially careful of this when using the [`no_reset` flag](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. #### White and Black Listing Variables @@ -387,7 +387,7 @@ These methods are primarily designed to help debug code and specs. #### `c?` -Shorthand for `on_console`, useful for entering expressions in pry console, to investigate the state of the client. +Shorthand for `on_client`, useful for entering expressions in the pry console, to investigate the state of the client. ```ruby pry:> c? { puts 'hello on the console' } # prints hello on the client @@ -432,7 +432,7 @@ end #### `open_in_chrome` By default specs are run with headless chrome, so there is no visible browser window. The `open_in_chrome` method will open a browser window, and load it with the current state. -You can also run specs in a visible chrome window by setting the `DRIVER` environment variable to `CHROME` +You can also run specs in a visible chrome window by setting the `DRIVER` environment variable to `chrome`. i.e. (`DRIVER=chrome bundle exec rspec ...`) #### `pause` The method is typically not needed assuming you are using a multithreaded server like Puma. If for whatever reason the pry debug session is not multithreaded, *and* you want to try some kind of experiment on the javascript console, *and* those experiments make requests to the server, you may not get a response, because all threads are in use. diff --git a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb index 49e060210..a10608b1b 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb @@ -181,7 +181,7 @@ def connect(*args) it 'opens the connection' do mount 'TestComponent' evaluate_ruby 'Hyperstack.go_ahead_and_connect' - Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in - 1) wait_for do sleep 0.25 Hyperstack::Connection.active @@ -201,7 +201,7 @@ def connect(*args) it 'sees the connection going offline' do mount 'TestComponent' evaluate_ruby 'Hyperstack.go_ahead_and_connect' - Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in - 1) wait_for do sleep 0.25 Hyperstack::Connection.active @@ -260,7 +260,7 @@ def connect(*args) it 'opens the connection' do mount 'TestComponent' evaluate_ruby 'Hyperstack.go_ahead_and_connect' - Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in) + Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in - 1) wait_for do sleep 0.25 Hyperstack::Connection.active From bae7a8a3f924477f49e2d9123309146b68f0bd92 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 14 Mar 2021 17:07:25 -0400 Subject: [PATCH 197/307] added sinatra sample app and support and doc --- docs/hyper-spec/04-using-with-rack.md | 56 ++++++ ruby/examples/misc/sinatra_app/Gemfile | 14 ++ ruby/examples/misc/sinatra_app/Gemfile.lock | 159 ++++++++++++++++++ ruby/examples/misc/sinatra_app/app.rb | 32 ++++ .../misc/sinatra_app/app/application.rb | 3 + ruby/examples/misc/sinatra_app/config.ru | 3 + .../misc/sinatra_app/spec/spec_helper.rb | 18 ++ .../misc/sinatra_app/spec/test_spec.rb | 6 + ruby/hyper-spec/lib/hyper-spec/helpers.rb | 4 +- 9 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 docs/hyper-spec/04-using-with-rack.md create mode 100644 ruby/examples/misc/sinatra_app/Gemfile create mode 100644 ruby/examples/misc/sinatra_app/Gemfile.lock create mode 100644 ruby/examples/misc/sinatra_app/app.rb create mode 100644 ruby/examples/misc/sinatra_app/app/application.rb create mode 100644 ruby/examples/misc/sinatra_app/config.ru create mode 100644 ruby/examples/misc/sinatra_app/spec/spec_helper.rb create mode 100644 ruby/examples/misc/sinatra_app/spec/test_spec.rb diff --git a/docs/hyper-spec/04-using-with-rack.md b/docs/hyper-spec/04-using-with-rack.md new file mode 100644 index 000000000..7c5147f40 --- /dev/null +++ b/docs/hyper-spec/04-using-with-rack.md @@ -0,0 +1,56 @@ +# Using Hyperspec with Rack + +Hyperspec will run with Rails out of the box, but you can also use Hyperspec with any Rack application, with just a little more setup. For example here is a sample configuration setup with Sinatra: + +```ruby +# Gemfile +... + +gem "sinatra" +gem "rspec" +gem "pry" +gem "opal" +gem "opal-sprockets" +gem "rack" +gem "puma" +group :test do + # gem 'hyper-spec', '~> 1.0.alpha1.0' + # or to use edge: + gem 'hyper-spec', + git: 'git://github.com/hyperstack-org/hyperstack.git', + branch: 'edge', + glob: 'ruby/*/*.gemspec' +end +``` + +```ruby +# spec/spec_helper.rb + +require "bundler" +Bundler.require +ENV["RACK_ENV"] ||= "test" + +# require your application files as needed +require File.join(File.dirname(__FILE__), "..", "app.rb") + +# bring in needed support files +require "rspec" +require "rack/test" +require "hyper-spec/rack" + +# assumes your sinatra app is named app +Capybara.app = HyperSpecTestController.wrap(app: app) + +set :environment, :test +set :run, false +set :raise_errors, true +set :logging, false +``` + +### Details + +The interface between Hyperspec and your application environment is defined by the `HyperspecTestController` class. This file typically includes a set of helper methods from `HyperSpec::ControllerHelpers`, which can then be overridden to give whatever behavior your specific framework needs. Have a look at the `hyper-spec/rack.rb` and `hyper-spec/controller_helpers.rb` files in the Hyperspec gem directory. + +### Example + +A complete (but very simple) example is in this repos `ruby/examples/misc/sinatra_app` directory diff --git a/ruby/examples/misc/sinatra_app/Gemfile b/ruby/examples/misc/sinatra_app/Gemfile new file mode 100644 index 000000000..f95cfe828 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/Gemfile @@ -0,0 +1,14 @@ +source "https://rubygems.org" + +gem "sinatra" +gem "rspec" +gem "pry" +gem "opal" +gem "opal-sprockets" +gem "rack" +gem "puma" +gem "hyper-spec", path: "../../../hyper-spec" +# gem 'hyper-spec', +# git: 'git://github.com/hyperstack-org/hyperstack.git', +# branch: 'edge', +# glob: 'ruby/*/*.gemspec' diff --git a/ruby/examples/misc/sinatra_app/Gemfile.lock b/ruby/examples/misc/sinatra_app/Gemfile.lock new file mode 100644 index 000000000..72b796b6e --- /dev/null +++ b/ruby/examples/misc/sinatra_app/Gemfile.lock @@ -0,0 +1,159 @@ +PATH + remote: ../../../hyper-spec + specs: + hyper-spec (1.0.alpha1.5) + actionview + capybara + chromedriver-helper (= 1.2.0) + filecache + method_source + opal (>= 0.11.0, < 2.0) + parser (>= 2.3.3.1) + rspec + selenium-webdriver + timecop (~> 0.8.1) + uglifier + unparser (>= 0.4.2) + webdrivers + +GEM + remote: https://rubygems.org/ + specs: + actionview (6.1.3) + activesupport (= 6.1.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activesupport (6.1.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + archive-zip (0.12.0) + io-like (~> 0.3.0) + ast (2.4.2) + builder (3.2.4) + capybara (3.35.3) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + childprocess (3.0.0) + chromedriver-helper (1.2.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.3) + concurrent-ruby (1.1.8) + crass (1.0.6) + diff-lcs (1.4.4) + erubi (1.10.0) + execjs (2.7.0) + filecache (1.0.2) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + io-like (0.3.1) + loofah (2.9.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + method_source (1.0.0) + mini_mime (1.0.2) + mini_portile2 (2.5.0) + minitest (5.14.4) + mustermann (1.1.1) + ruby2_keywords (~> 0.0.1) + nio4r (2.5.7) + nokogiri (1.11.2) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) + opal (1.1.1) + ast (>= 2.3.0) + parser (~> 3.0) + opal-sprockets (1.0.0) + opal (>= 1.0, < 1.2) + sprockets (~> 4.0) + tilt (>= 1.4) + parser (3.0.0.0) + ast (~> 2.4.1) + pry (0.14.0) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.6) + puma (5.2.2) + nio4r (~> 2.0) + racc (1.5.2) + rack (2.2.3) + rack-protection (2.1.0) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + regexp_parser (2.1.1) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.2) + ruby2_keywords (0.0.4) + rubyzip (2.3.0) + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + sinatra (2.1.0) + mustermann (~> 1.0) + rack (~> 2.2) + rack-protection (= 2.1.0) + tilt (~> 2.0) + sprockets (4.0.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + tilt (2.0.10) + timecop (0.8.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + unparser (0.6.0) + diff-lcs (~> 1.3) + parser (>= 3.0.0) + webdrivers (4.6.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.4.2) + +PLATFORMS + ruby + +DEPENDENCIES + hyper-spec! + opal + opal-sprockets + pry + puma + rack + rspec + sinatra + +BUNDLED WITH + 2.1.4 diff --git a/ruby/examples/misc/sinatra_app/app.rb b/ruby/examples/misc/sinatra_app/app.rb new file mode 100644 index 000000000..e6897f9be --- /dev/null +++ b/ruby/examples/misc/sinatra_app/app.rb @@ -0,0 +1,32 @@ +require "bundler" +Bundler.require + +module OpalSprocketsServer + def self.opal + @opal ||= Opal::Sprockets::Server.new do |s| + s.append_path "app" + s.main = "application" + s.debug = ENV["RACK_ENV"] != "production" + end + end +end + +get "/" do + <<~HTML + + + + #{Opal::Sprockets.javascript_include_tag('application', debug: OpalSprocketsServer.opal.debug, sprockets: OpalSprocketsServer.opal.sprockets, prefix: '/assets')} + + + HTML +end + +def app + Rack::Builder.app do + map "/assets" do + run OpalSprocketsServer.opal.sprockets + end + run Sinatra::Application + end +end diff --git a/ruby/examples/misc/sinatra_app/app/application.rb b/ruby/examples/misc/sinatra_app/app/application.rb new file mode 100644 index 000000000..42ddc8e00 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/app/application.rb @@ -0,0 +1,3 @@ +require 'opal' + +puts 'hello world' diff --git a/ruby/examples/misc/sinatra_app/config.ru b/ruby/examples/misc/sinatra_app/config.ru new file mode 100644 index 000000000..565f58587 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/config.ru @@ -0,0 +1,3 @@ +require "./app.rb" + +run app diff --git a/ruby/examples/misc/sinatra_app/spec/spec_helper.rb b/ruby/examples/misc/sinatra_app/spec/spec_helper.rb new file mode 100644 index 000000000..5a5daab18 --- /dev/null +++ b/ruby/examples/misc/sinatra_app/spec/spec_helper.rb @@ -0,0 +1,18 @@ +# spec/spec_helper.rb + +require "bundler" +Bundler.require +ENV["RACK_ENV"] ||= "test" + +require File.join(File.dirname(__FILE__), "..", "app.rb") + +require "rspec" +require "rack/test" +require "hyper-spec/rack" + +Capybara.app = HyperSpecTestController.wrap(app: app) + +set :environment, :test +set :run, false +set :raise_errors, true +set :logging, false diff --git a/ruby/examples/misc/sinatra_app/spec/test_spec.rb b/ruby/examples/misc/sinatra_app/spec/test_spec.rb new file mode 100644 index 000000000..d5353b91f --- /dev/null +++ b/ruby/examples/misc/sinatra_app/spec/test_spec.rb @@ -0,0 +1,6 @@ +require "spec_helper" +describe "The App", no_reset: true, js: true do + it "works" do + expect { 12 + 12 }.on_client_to eq 24 + end +end diff --git a/ruby/hyper-spec/lib/hyper-spec/helpers.rb b/ruby/hyper-spec/lib/hyper-spec/helpers.rb index ac881d813..c5d05df04 100644 --- a/ruby/hyper-spec/lib/hyper-spec/helpers.rb +++ b/ruby/hyper-spec/lib/hyper-spec/helpers.rb @@ -95,7 +95,9 @@ def client_option(opts = {}) end def default_arity_check - Rails&.application&.config&.opal&.arity_check_enabled if defined? Rails + Rails.application.config.opal.arity_check_enabled if defined? Rails + rescue StandardError + false end alias client_options client_option From 10adb71b9b19d1fe0da1f8584613680651c57e70 Mon Sep 17 00:00:00 2001 From: Mitch VanDuyn Date: Sun, 14 Mar 2021 19:21:55 -0400 Subject: [PATCH 198/307] Update html-css.md test update --- docs/client-dsl/html-css.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index df6ed1876..eb14c5d06 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -8,7 +8,7 @@ A Hyperstack user-interface is composed of HTML elements, conditional logic and ```ruby UL do - 10.times { |n| LI { "Number #{n}" }} + 5.times { |n| LI { "Number #{n}" }} end ``` @@ -86,4 +86,4 @@ For `style` you need to pass a hash: ```ruby PARA(style: { display: item[:some_property] == "some state" ? :block : :none }) -``` \ No newline at end of file +``` From 1975f42418e4005684226111057f7f59f45a87d5 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 14 Mar 2021 20:05:50 -0400 Subject: [PATCH 199/307] updated hyper-spec docs --- docs/SUMMARY.md | 7 +- docs/development-workflow/hyper-spec.md | 422 ----------------- docs/hyper-spec/01-installation.md | 57 --- docs/hyper-spec/02-tutorial.md | 122 ----- .../03-hyperspec-methods-and-features.md | 440 ------------------ docs/hyper-spec/04-using-with-rack.md | 56 --- docs/hyper-spec/README.md | 41 -- 7 files changed, 5 insertions(+), 1140 deletions(-) delete mode 100644 docs/development-workflow/hyper-spec.md delete mode 100644 docs/hyper-spec/01-installation.md delete mode 100644 docs/hyper-spec/02-tutorial.md delete mode 100644 docs/hyper-spec/03-hyperspec-methods-and-features.md delete mode 100644 docs/hyper-spec/04-using-with-rack.md delete mode 100644 docs/hyper-spec/README.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 479a46bd8..5b96f359b 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -23,10 +23,13 @@ * [Development Workflow](development-workflow/README.md) * [Debugging](development-workflow/debugging.md) * [Internationalization](development-workflow/hyper-i18n.md) - * [HyperSpec](development-workflow/hyper-spec.md) + * [HyperSpec](development-workflow/hyper-spec/README.md) + * [Installation](development-workflow/hyper-spec/01-installation.md) + * [Tutorial](development-workflow/hyper-spec/02-tutorial.md) + * [Methods and Features](development-workflow/hyper-spec/03-methods-and-features.md) + * [Using with Rack](development-workflow/hyper-spec/04-using-with-rack.md) * [Tools](development-workflow/tools.md) * [Tutorial](tutorial/README.md) * [TodoMVC Tutorial Part I](tutorial/todo.md) * [TodoMVC Tutorial Part II](tutorial/todo-part-2.md) * [Community](community.md) - diff --git a/docs/development-workflow/hyper-spec.md b/docs/development-workflow/hyper-spec.md deleted file mode 100644 index a6341f6a3..000000000 --- a/docs/development-workflow/hyper-spec.md +++ /dev/null @@ -1,422 +0,0 @@ -# HyperSpec - -With HyperSpec you can run _isomorphic_ specs for all your Hyperstack code using RSpec. Everything runs as standard RSpec test specs. - -For example if you have a component like this: - -```ruby -class SayHello < HyperComponent - param :name - render(DIV) do - "Hello there #{name}" - end -end -``` - -Your test spec would look like this: - -```ruby -describe 'SayHello', js: true do - it 'has the correct content' do - mount "SayHello", name: 'Fred' - expect(page).to have_content('Hello there Fred') - end -end -``` - -The `mount` method will setup a blank client window, and _mount_ the named component in the window, passing any parameters. - -Notice that the spec will need a client environment so we must set `js: true`. - -The `mount` method can also take a block which will be recompiled and sent to the client before mounting the component. You can place any client side code in the mount block including the definition of components. - -```ruby -describe "the mount's code block", js: true do - it 'will be recompiled on the client' do - mount 'ShowOff' do - class ShowOff < HyperComponent - render(DIV) { 'Now how cool is that???' } - end - end - expect(page).to have_content('Now how cool is that???' ) - end -end -``` - -## Why? - -Hyperstack wants to make the server-client divide as transparent to the developer as practical. Given this, it makes sense that the testing should also be done with as little concern for client versus server. - -HyperSpec allows you to directly use tools like FactoryBot \(or Hyperstack Operations\) to setup some test data, then run a spec to make sure that a component correctly displays, or modifies that data. You can use Timecop to manipulate time and keep in sync between the server and client. This makes testing easier and more realistic without writing a lot of redundant code. - -## Installation - -These instructions are assuming you are using Rails as the backend. However the `hyper-spec` gem itself does not require Rails, so you can adapt these instructions as needed. - -### Add the gems - -In your `Gemfile` add - -```ruby -group :test do - gem 'hyper-spec', path: '../hyperstack/ruby/hyper-spec' - gem 'database_cleaner' # optional but we find it works best due to the concurrency of hyperstack -end -``` - -and `bundle install` - -### Install RSpec files - -`bundle exec rails g rspec:install` - -> Skip this step if rspec is already installed. - -### Configure HyperSpec - -Update your `spec/rails_helper.rb` so it looks like this: - -```ruby -# This file is copied to spec/ when you run 'rails generate rspec:install' -require 'spec_helper' -ENV['RAILS_ENV'] ||= 'test' -require File.expand_path('../../config/environment', __FILE__) -# Prevent database truncation if the environment is production -abort("The Rails environment is running in production mode!") if Rails.env.production? -require 'rspec/rails' -# Add additional requires below this line. Rails is not loaded until this point! - -# THESE ARE LINES WE ARE ADDING -# JUST MAKE SURE THEY ARE AFTER `require 'rspec/rails'` - -# pull in the hyper-spec code. -require 'hyper-spec' - -# If you are using DatabaseCleaner here is where -# you set the mode. We recommend truncation. -DatabaseCleaner.strategy = :truncation - -# Now we setup Rspec details -RSpec.configure do |config| - - # This is only needed if you are using DatabaseCleaner - config.before(:each) do - DatabaseCleaner.clean - end - - # If you are NOT using webpacker remove this block - config.before(:suite) do # compile front-end - Webpacker.compile - end -end -... -``` - -### Create an install smoke test - -Make sure your installation is working by creating a simple smoke test like this: - -```ruby -# spec/hyperspec_smoke_test.rb -require 'rails_helper' - -describe 'Hyperspec', js: true do - it 'can mount and test a component' do - mount "HyperSpecTest" do - class HyperSpecTest < HyperComponent - render(DIV) do - "It's Alive!" - end - end - end - expect(page).to have_content("It's Alive!") - end - it 'can evaluate and test expressions on the client' do - expect_evaluate_ruby do - [1, 2, 3].reverse - end.to eq [3, 2, 1] - end -end -``` - -To run it do a -`bundle exec rspec spec/hyperspec_smoke_test.rb` - -> Note that because the test does not end in `_spec.rb` it will not be run with the rest of your specs. - -## Environment Variables - -You can set `DRIVER` to `chrome` to run the client in chrome and see what is going on. By default tests will run in chrome headless mode which is quicker, but harder to debug problems. - -```text -DRIVER=chrome bundle exec rspec -``` - -## Spec Helpers - -HyperSpec adds the following spec helpers to your test environment - -* `mount` -* `client_option` and `client_options` -* `on_client` -* `isomorphic` -* `evaluate_ruby` -* `expect_evaluate_ruby` -* `expect_promise` -* call back and event history methods -* `pause` -* `attributes_on_client` -* `size_window` -* `add_class` - -#### The `mount` Method - -`mount` takes the name of a component, prepares an empty test window, and mounts the named component in the window. -You may give a block to `mount` which will be recompiled on the client, and run _before_ mounting. This means that the component mounted may be actually defined in the block, which is useful for setting up top level wrapper components, which will invoke your component under test. You can also modify existing components for white box testing, or local fixture data, constants, etc. - -`mount` may also be given a hash of the parameters to be passed to the component. - -```ruby -mount 'Display', test: 123 do - class Display < HyperComponent - param :test - render(DIV) { test.to_s } - end -end -``` - -#### The `client_option` Method - -There are several options that control the mounting process. Use `client_option` \(or `client_options`\) before accessing any client side to set any of these options: - -* `render_on`: `:server_only`, `:client_only`, or `:both`, default is client\_only. -* `layout`: specify the layout to be used. Default is :none. -* `style_sheet`: specify the name of the style sheet to be loaded. Defaults to the application stylesheet. -* `javascript`: specify the name of the javascript asset file to be loaded. Defaults to the application js file. - -For example: - -```ruby -it "can be rendered server side only" do - client_option render_on: :server_only - mount 'SayHello', name: 'George' - expect(page).to have_content('Hello there George') - # Server only means no code is downloaded to the client - expect(evaluate_script('typeof React')).to eq('undefined') -end -``` - -If you need to pull in alternative style sheets and javascript files, the recommended way to do this is to - -1. Add them to a `specs/assets/stylesheets` and `specs/assets/javascripts` directory and -2. Add the following line to your `config/environment/test.rb` file: - - ```ruby - config.assets.paths << ::Rails.root.join('spec', 'assets', 'stylesheets').to_s - config.assets.paths << ::Rails.root.join('spec', 'assets', 'javascripts').to_s - ``` - -This way you will not pollute your application with these 'test only' files. - -_The javascript spec asset files can be `.rb` files and contain ruby code as well. See the specs for examples!_ - -#### The `on_client` Method - -`on_client` takes a block and compiles and runs it on the client. This is useful in setting up test constants and client only fixtures. - -Note that `on_client` needs to _proceed_ any calls to `mount`, `evaluate_ruby`, `expect_evaluate_ruby` or `expect_promise` as these methods will initiate the client load process. - -#### The `isomorphic` Method - -Similar to `on_client` but the block is _also_ run on the server. This is useful for setting constants shared by both client and server, and modifying behavior of isomorphic classes such as ActiveRecord models, and HyperOperations. - -```ruby -isomorphic do - class SomeModel < ActiveRecord::Base - def fake_attribute - 12 - end - end -end -``` - -#### The `evaluate_ruby` Method - -Takes either a string or a block, dynamically compiles it, downloads it to the client and runs it. - -```ruby -evaluate_ruby do - i = 12 - i * 2 -end -# returns 24 - -isomorphic do - def factorial(n) - n == 1 ? 1 : n * factorial(n-1) - end -end - -expect(evaluate_ruby("factorial(5)")).to eq(factorial(5)) -``` - -`evaluate_ruby` can also be very useful for debug. Set a breakpoint in your test, then use `evaluate_ruby` to interrogate the state of the client. - -#### The `expect_evaluate_ruby` Method - -Combines expect and evaluate methods: - -```ruby -expect_evaluate_ruby do - i = 1 - 5.times { |n| i = i*n } - i -end.to eq(120) -``` - -#### The `expect_promise` Method - -Works like `expect_evaluate_ruby` but is used with promises. `expect_promise` will hang until the promise resolves and then return to the results. - -```ruby -expect_promise do - Promise.new.tap do |p| - after(2) { p.resolve('hello') } - end -end.to eq('hello') -``` - -#### Call Back and Event History Methods - -HyperReact components can _generate_ events and perform callbacks. HyperSpec provides methods to test if an event or callback was made. - -```ruby -mount 'CallBackOnEveryThirdClick' do - class CallBackOnEveryThirdClick < HyperComponent - fires :click3 - def increment_click - @clicks ||= 0 - @clicks = (@clicks + 1) - click3!(@clicks) if @clicks % 3 == 0 - end - render do - DIV(class: :tp_clicker) { "click me" } - .on(:click) { increment_click } - end - end -end - -7.times { page.click('#tp_clicker') } -expect(callback_history_for(:click3)).to eq([[3], [6]]) -``` - -* `callback_history_for`: the entire history given as an array of arrays -* `last_callback_for`: same as `callback_history_for(xxx).last` -* `clear_callback_history_for`: clears the array \(userful for repeating test variations without remounting\) -* `event_history_for, last_event_for, clear_event_history_for`: same but for events. - -#### The `pause` Method - -For debugging. Everything stops, until you type `go()` in the client console. Running `binding.pry` also has this effect, and is often sufficient, however it will also block the server from responding unless you have a multithreaded server. - -#### The `attributes_on_client` Method - -_This feature is currently untested - use at your own risk._ - -This reads the value of active record model attributes on the client. - -In other words the method `attributes_on_client` is added to all ActiveRecord models. You then take a model you have instance of on the server, and by passing the Capybara page object, you get back the attributes for that same model instance, currently on the client. - -```ruby -expect(some_record_on_server.attributes_on_client(page)[:fred]).to eq(12) -``` - -> Note that after persisting a record the client and server will be synced so this is mainly useful for debug or in rare cases where it is important to interrogate the value on the client before its persisted. - -#### The `size_window` Method - -Sets the size of the test window. You can say: `size_window(width, height)` or pass one of the following standard sizes: to one of the following standard sizes: - -* small: 480 X 320 -* mobile: 640 X 480 -* tablet: 960 X 640 -* large: 1920 X 6000 -* default: 1024 X 768 - -example: `size_window(:mobile)` - -You can also modify the standard sizes with `:portrait` - -example: `size_window(:table, :portrait)` - -You can also specify the size by providing the width and height. - -example: `size_window(600, 600)` - -size\_window with no parameters is the same as `size_window(:default)` - -Typically you will use this in a `before(:each)` or `before(:step)` block - -#### The `add_class` Method - -Sometimes it's useful to change styles during testing \(mainly for debug so that changes on screen are visible.\) - -The `add_class` method takes a class name \(as a symbol or string\), and hash representing the style. - -```ruby -it "can add classes during testing" do - add_class :some_class, borderStyle: :solid - mount 'StyledDiv' do - class StyledDiv < HyperComponent - render(DIV, id: 'hello', class: 'some_class') do - 'Hello!' - end - end - end - expect(page.find('#hello').native.css_value('border-right-style')).to eq('solid') -end -``` - -## Integration with the Steps gem - -The [rspec-steps gem](https://github.com/LRDesign/rspec-steps) can be useful in doing client side testing. Without rspec-steps, each test spec will cause a reload of the browser window. While this insures that each test runs in a clean environment, it is typically not necessary and can really slow down testing. - -The rspec-steps gem will run each test without reloading the window, which is usually fine. - -Checkout the rspec-steps example in the `hyper_spec.rb` file for an example. - -> Note that hopefully in the near future we are going to build a custom capybara driver that will just directly talk to Hyperstack on the client side. Once this is in place these troubles should go away! - Volunteers welcome to help!\* - -## Timecop Integration - -HyperSpec is integrated with [Timecop](https://github.com/travisjeffery/timecop) to freeze, move and speed up time. The client and server times will be kept in sync when you use any these Timecop methods: - -* `freeze`: Freezes time at the specified point in time \(default is Time.now\) -* `travel`: Time runs normally forward from the point specified. -* `scale`: Like travel but times runs faster. -* `return`: Return to normal system time. - -For example: - -```ruby -Timecop.freeze # freeze time at current time -# ... test some stuff -Timecop.freeze Time.now+10.minutes # move time forward 10 minutes -# ... check to see if expected events happened etc -Timecop.return -``` - -```ruby -Timecop.scale 60, Time.now-1.year do - # Time will begin 1 year ago but advance 60 times faster than normal - sleep 10 - # still sleeps for 10 seconds YOUR time, but server and client will - # think 10 minutes have passed -end -# no need for Timecop.return if using the block style -``` - -See the Timecop [README](https://github.com/travisjeffery/timecop/blob/master/README.markdown) for more details. - -> There is one confusing thing to note: On the server if you `sleep` then you will sleep for the specified number of seconds when viewed _outside_ of the test. However inside the test environment if you look at Time.now, you will see it advancing according to the scale factor. Likewise if you have a `after` or `every` block on the client, you will wait according to _simulated_ time. - diff --git a/docs/hyper-spec/01-installation.md b/docs/hyper-spec/01-installation.md deleted file mode 100644 index 11a2b79d9..000000000 --- a/docs/hyper-spec/01-installation.md +++ /dev/null @@ -1,57 +0,0 @@ -# HyperSpec Installation - -Add `gem 'hyper-spec'` to your Gemfile in the usual way. Typically in a Rails app you will add this in the test section of your Gemfile: - -```ruby -group :test do - gem 'hyper-spec', '~> 1.0.alpha1.0' -end -``` - -Make sure to `bundle install`. - -> Note: if you want to use the unreleased edge branch your hyper-spec gem specification will be: -> -> ```ruby -> gem 'hyper-spec', -> git: 'git://github.com/hyperstack-org/hyperstack.git', -> branch: 'edge', -> glob: 'ruby/*/*.gemspec' -> ``` - -HyperSpec is integrated with the `pry` gem for debugging, so it is recommended to add the `pry` gem as well. - -HyperSpec will also use the `timecop` gem if present to allow you to control and synchronize time on the server and the client. - -A typical spec_helper file when using HyperSpec will look like this: - -```ruby -# spec_helper.rb -require 'hyper-spec' -require 'pry' # optional - -ENV["RAILS_ENV"] ||= 'test' -require File.expand_path('../test_app/config/environment', __FILE__) - -require 'rspec/rails' -require 'timecop' # optional - -# any other rspec configuration you need -# note HyperSpec will include chrome driver for providing the client -# run time environment -``` - -To load the webdriver and client environment your spec should have the -`:js` flag set: - -```ruby -# the js flag can be set on the entire group of specs, or a context -describe 'some hyper-specs', :js do - ... -end - -# or for an individual spec - it 'an individual hyper-spec', :js do - ... - end -``` diff --git a/docs/hyper-spec/02-tutorial.md b/docs/hyper-spec/02-tutorial.md deleted file mode 100644 index d988dc86c..000000000 --- a/docs/hyper-spec/02-tutorial.md +++ /dev/null @@ -1,122 +0,0 @@ -# Tutorial - -For this quick tutorial lets assume you have an existing Rails app that -already uses RSpec to which you have added a first Hyperstack component to -try things out. - -For your trial, you have created a very simple component that shows -the number of orders shipped by your companies website: - -```ruby -class OrdersShipped < HyperComponent - def format_number(number) - number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse - end - - render(DIV, class: 'orders-shipped') do - format_number Order.shipped.count - end -end -``` - -> Note that styling can be taken care of in the usual way by -> providing styles for the `orders-shipped` css class. All we care -> about here is the *function* of the component. - -Meanwhile `Order` is an ActiveRecord Model that would look something like this: - -```ruby -class Order < ApplicationRecord - ... - scope :shipped, -> () { where(status: :shipped) } - ... -end -``` - -> Note that when using ActiveRecord models in your specs you will -> need to add the appropriate database setup and cleaner methods like you would -> for any specs used with ActiveRecord. We assume here that as each -> spec starts there are no records in the database - -The `OrdersShipped` component can be mounted on any page of your site, -and assuming the proper policy permissions are provided it will -show the total orders shipped, and will dynamically increase in -realtime. - -A partial spec for this component might look like this: - -```ruby -require 'spec_helper' - -describe 'OrdersShipped', :js do - it 'dynamically displays the orders shipped' do - mount 'OrdersShipped' - expect(find('div.orders-shipped')).to have_content(0) - Order.create(status: :shipped) - expect(find('div.orders-shipped')).to have_content(1) - Order.last.destroy - expect(find('div.orders-shipped')).to have_content(0) - end - - it '#format method' do - on_client { @comp = OrdersShipped.new } - ['1,234,567', '123', '1,234'].each do |n| - expect { @comp.format_number(n.gsub(',','').to_i) } - .on_client_to eq(n) - end - end -end -``` - -If you are familiar with Capybara then the first spec should -look similar to an integration spec. The difference is instead -of visiting a page, we `mount` the `OrdersShipped` component on a blank page -that hyper-spec will set up for us. This lets us unit test -components outside of any application specific view logic. - -> Note that like Capybara we indicate that a client environment should -> be set up by adding the :js tag. - -Once mounted we can use Capybara finders and matchers to check -if our content is as expected. Because we are running on the server -we can easily add and delete orders, and check the response on the UI. - -The second spec shows how we can do some white box unit testing of our -component. Instead of mounting the component we just create a new -instance which will be invisible since it was not mounted. For this we -use the `on_client` method. - -The `on_client` method takes a block, and will -compile that block using -Opal, and execute it on the client. In this case we simply create a -new `OrderShipped` instance, and assign it to an instance variable, which as you -will see will continue to be available to us later in the spec. - -> Note, if you are an RSpec purist, you would probably prefer to see -> something like `let` be used here instead of an instance variable. Shall we -> say its on the todo list. - -Now that we have our test component setup we can test its `format_number` -method. To do this we put the test expression in a block followed by -`on_client_to`. Again the block will be compiled using Opal, executed on -the client, and the result will be returned to the expectation. - -Notice that the server side variable `n` can be read (but not written) within -the client block. All local variables, memoized variables, and instance variables can -can be read in the client block as long as they represent objects that can be -sensibly marshalled and unmarshalled. - -This has covered the basics of Hyperspec - in summary: - -+ The `js` tag indicates the spec will be using a client environment. -+ `mount`: Mount a component on a blank page. This replaces the `visit` method -for unit testing components. -+ `on_client`: Execute Ruby code on the client (and return the result). -+ `on_client_to`: Execute the expectation block on the client, and then check -the expectation (on the server.) -+ Instance variables retain their values between client execution blocks. -+ All variables accessible to the spec are copied to the client if possible. - -There are many other features such as dealing with promises, passing data to -and from a mounted component, using the `Timecop` gem, and working with a `pry` -session. So read on. diff --git a/docs/hyper-spec/03-hyperspec-methods-and-features.md b/docs/hyper-spec/03-hyperspec-methods-and-features.md deleted file mode 100644 index 8fec6d0c6..000000000 --- a/docs/hyper-spec/03-hyperspec-methods-and-features.md +++ /dev/null @@ -1,440 +0,0 @@ -# HyperSpec Methods and Features - -### Expectation Helpers - -These can be used any where within your specs: - -+ [`on_client`](#the-on_client-method) - executes code on the client -+ [`isomorphic`](#the-isomorphic-method) - executes code on the client *and* the server -+ [`mount`](#mounting-components) - mounts a hyperstack component in an empty window -+ [`before_mount`](#before_mount) - specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client` -+ [`insert_html`](#insert_html) - insert some html into a page -+ [`client_options`](#client-initialization-options) - allows options to be specified globally -+ [`run_on_client`](#run_on_client) - same as `on_client` but no value is returned -+ [`reload_page`](#reload_page) - resets the page environment -+ [`add_class`](#add_class) - adds a CSS class -+ [`size_window`](#size_window) - specifies how big the client window should be -+ [`attributes_on_client`](#attributes_on_client) - returns any ActiveModel attributes loaded on the client - -These methods are used after mounting a component to retrieve -events sent outwards from the component: - -+ [`callback_history_for`](#retrieving-event-data-from-the-mounted-component) -+ [`last_callback_for`](#retrieving-event-data-from-the-mounted-component) -+ [`clear_callback_history_for`](#retrieving-event-data-from-the-mounted-component) -+ [`event_history_for`](#retrieving-event-data-from-the-mounted-component) -+ [`last_event_for`](#retrieving-event-data-from-the-mounted-component) -+ [`clear_event_history_for`](#retrieving-event-data-from-the-mounted-component) - -### Expectation Targets - -These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. - -+ [`on_client_to`](#client-expectation-targets), [`to_on_client_not`](#client-expectation-targets) - the expression will be evaluated on the client, and matched on the server. - -These methods have the following aliases to make your specs more readable: -+ [`to_on_client`](#client-expectation-targets) -+ [`on_client_to_not`](#client-expectation-targets) -+ [`on_client_not_to`](#client-expectation-targets) -+ [`to_not_on_client`](#client-expectation-targets) -+ [`not_to_on_client`](#client-expectation-targets) -+ [`to_then`](#client-expectation-targets) -+ [`then_to_not`](#client-expectation-targets) -+ [`then_not_to`](#client-expectation-targets) -+ [`to_not_then`](#client-expectation-targets) -+ [`not_to_then`](#client-expectation-targets) - -in addition -+ [`with`](#client-expectation-targets) - can be chained with the above methods to pass data to initialize local variables on the client - -### Other Debugging Aids - -The following methods are used primarly at a debug break point, most require you use binding.pry as your debugger: - -+ [`to_js`](#to_js) - returns the ruby code compiled to JS. -+ [`c?`](#c?) - alias for `on_client`. -+ [`ppr`](#ppr) - print the results of the ruby expression on the client console. -+ [`debugger`](#debugger) - Sets a debug breakpoint on code running on the client. -+ [`open_in_chrome`](#open_in_chrome) - Opens a chrome browser that will load the current state. -+ [`pause`](#pause) - Halts execution on the server without blocking I/O. - -### Available Webdrivers - -HyperSpec comes integrated with Chrome and Chrome headless webdrivers. The default configuration will run using Chrome headless. To see what is going on set the `DRIVER` environment variable to `chrome` -```bash -DRIVER=chrome bundle exec rspec -``` - -### Timecop Integration - -You can use the [`timecop` gem](https://github.com/travisjeffery/timecop) to control the flow of time within your specs. Hyperspec will coordinate things with the client so the time on the client is kept in sync with the time on the server. So for example if you use Timecop to advance time 1 day on the server, time on the browser will also advance by one day. - -See the [Client Initialization Options](#client-initialization-options) section for how to control the client time zone, and clock resolution. - -### The `no_reset` flag - -By default the client environment will be reinitialized at the beginning of every spec. If this is not needed you can speed things up by adding the `no_reset` flag to a block of specs. - -# Details - -### The `on_client` method - -The on_client method takes a block. The ruby code inside the block will be executed on the client, and the result will be returned. - -```ruby - it 'will print a message on the client' do - on_client do - puts 'hey I am running here on the client!' - end - end -``` - -If the block returns a promise Hyperspec will wait for the promise to be resolved (or rejected) before returning. For example: - -```ruby - it 'waits for a promise' do - start_time = Time.now - result = on_client do - promise = Promise.new - after(10.seconds) { promise.resolve('done!') } - promise - end - expect(result).to eq('done!') - expect(Time.now-start_time).to be >= 10.seconds - end -``` -> HyperSpec will do its best to reconstruct the result back on the server in some sensible way. Occasionally it just doesn't work, in which case you can end the block with a `nil` or some other simple expression, or use the `run_on_client` method, which does not return the result. - -### Accessing variables on the client - -It is often useful to pass variables from the spec to the client. Hyperspec will copy all your local variables, memoized variables, and instance variables known at the time the `on_client` block is compiled to the client.
-```ruby - let!(memoized) { 'a memoized variable' } - it 'will pass variables to the client' do - local = 'a local variable' - @instance = 'an instance variable' - result = on_client { [memoized, local, @instance] } - expect(result).to eq [memoized, local, @instance] - end -``` -> Note that memoized variables are not initialized until first -accessed, so you probably want to use the let! method unless you -are sure you are accessing the memoized value before sending it to the client. - -The value of instance variables initialized on the client are preserved -across blocks executed on the client. For example: -```ruby - it 'remembers instance variables' do - on_client { @total = 0 } - 10.times do |i| - # note how we are passing i in - on_client { @total += i } - end - result = on_client { @total } - expect(result).to eq(10 * 11 / 2) - end -``` - -> Be especially careful of this when using the [`no_reset` flag](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. - -#### White and Black Listing Variables - -By default all local variables, memoized variables, and instance variables in scope in the spec will be copied to the client. This can be controlled through the `include_vars` and `exclude_vars` [client options](#client-initialization-options). - -`include_vars` can be set to -+ an array of symbols: only those vars will be copied, -+ a single symbol: only that var will be copied, -+ any other truthy value: all vars will be copied (the default) -+ or nil, false, or an empty array: no vars will be copied. - -`exclude_vars` can be set to -+ an array of symbols - those vars will **not** be copied, -+ a single symbol - only that var will be excluded, -+ any other truthy value - no vars will be copied, -+ or nil, false, or an empty array - all vars will be copied (the default). - -Examples: - -```Ruby - # don't copy vars at all. - client_option exclude_vars: true - # only copy var1 and the instance var @var2 - client_option include_vars: [:var1, :@var2] - # only exclude foo_var - client_option exclude_vars: :foo_var -``` - -Note that the exclude_vars list will take precedence over the include_vars list. - -The exclude/include lists can be overridden on an individual call to on_client by providing a hash of names and values to on_client: - -```ruby - result = on_client(var: 12) { var * var } - expect(result).to eq(144) -``` - -You can do the same thing on expectations using the `with` method - See [Client Expectation Targets](#client-expectation-targets). - - -### The `isomorphic` method - -The `isomorphic` method works the same as `on_client` but in addition it also executes the same block on the server. It is especially useful when doing some testing of -ActiveRecord models, where you might want to modify the behavior of the model on server and the client. - -```ruby - it 'can run code the same everywhere!' do - isomorphic do - def factorial(x) - x.zero? ? 1 : x * factorial(x - 1) - end - end - - on_the_client = on_client { factorial(7) } - on_the_server = factorial(7) - expect(on_the_client).to eq(on_the_server) - end -``` - -### Client Initialization Options - -The first time a spec runs code on the client, it has to initialize a browser context. You can use the `client_options` (aka `client_option`) method to specify the following options when the page is loaded. - -+ `time_zone` - browsers always run in the local time zone, if you want to force the browser to act as if its in a different zone, you can use the time_zone option, and provide any valid zone that the rails `in_time_zone` method will accept.
-Example: `client_option time_zone: 'Hawaii'` -+ `clock_resolution`: Indicates the resolution that the simulated clock will run at on the client, when using the TimeCop gem. The default value is 20 (milliseconds). -+ `include_vars`: white list of all vars to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. -+ `exclude_vars`: black list of all vars not to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. -+ `render_on`: `:client_only` (default), `:server_only`, or `:both` -Hyperstack components can be prerendered on the server. The `render_on` option controls this feature. For example `server_only` is useful to insure components are properly prerendered. *See the `mount` method [below](#mounting-components) for more details on rendering components* -+ `no_wait`: After the page is loaded the system will by default wait until all javascript requests to the server complete before proceeding. Specifying `no_wait: true` will skip this. -+ `javascript`: The javascript asset to load when mounting the component. By default it will be `application` (.js is assumed). Note that the standard Hyperstack configuration will compile all the client side Ruby assets as well as javascript packages into the `application.js` file, so the default will work fine. -+ `style_sheet`: The style sheet asset to load when mounting the component. By default it will be `application` (.css is assumed). -+ `controller` - **(expert zone!)** specify a controller that will be used to mount the -component. By default hyper-spec will build a controller and route to handle the request from the client to mount the component. - -Any other options not listed above will be passed along to the Rail's controller `render` method. So for example you could specify some other specific layout using `client_option layout: 'special_layout'` - -Note that this method can be used in the `before(:each)` block of a spec context to provide options for all the specs in the block. - -### Mounting Components - -The `mount` method is used to render a component on a page: - -```ruby - it 'can display a component for me' do - mount 'SayHello', name: 'Lannar' do - class SayHello < HyperComponent - param :name - render(DIV) do - "Hello #{name}!" - end - end - end - - expect(page).to have_content('Hello Lannar') - end -``` - -The `mount` method has a few options. In it's simplest form you specify just the name of the component that is already defined in your hyperstack code and it will be mounted. - -You can add parameters that will be passed to the component as in the above example. As the above example also shows you can also define code within the block. This is just shorthand for defining the code before hand using `on_client`. The code does not have to be the component being mounted, but might be just some logic to help with the test. - -In addition `mount` can take any of the options provided to `client_options` (see above.) To provide these options, you must provide a (possibly) empty params hash. For example: -```ruby -mount 'MyComponent', {... params ... }, {... opts ... } -``` - -### Retrieving Event Data From the Mounted Component - -Components *receive* parameters, and may send callbacks and events back out. To test if a component has sent the appropriate data you can use the following methods: - -+ `callback_history_for` -+ `last_callback_for` -+ `clear_callback_history_for` -+ `event_history_for` -+ `last_event_for` -+ `clear_event_history_for` - -```ruby - it 'can check on a clients events and callbacks' do - mount 'BigTalker' do - class BigTalker < HyperComponent - fires :i_was_clicked - param :call_me_back, type: Proc - - before_mount { @click_counter = 0 } - - render(DIV) do - BUTTON { 'click me' }.on(:click) do - @click_counter += 1 - i_was_clicked! - call_me_back.call(@click_counter) - end - end - end - end - 3.times do - find('button').click - end - # the history is an array, one element for each item in the history - expect(event_history_for(:i_was_clicked).length).to eq(3) - # each item in the array is itself an array of the arguments - expect(last_call_back_for(:call_me_back)).to eq([3]) - # clearing the history resets the array to empty - clear_event_history_for(:i_was_clicked) - expect(event_history_for(:i_was_clicked).length).to eq(0) - end -``` - -> Note that you must declare the params as type `Proc`, or use -the `fires` method to declare an event for the history mechanism to work. - -### Other Helpers - -#### `before_mount` - -Specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client`. This is primarly useful to add to an rspec `before(:each)` block containing common client code needed by all the specs in the context. - -> Unlike `mount`, `isomorphic` and `on_client`, `before_mount` does not load the client page, but will wait for the first of the other methods to be called. - -#### `add_class` - -Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the React [style format.](https://reactjs.org/docs/dom-elements.html#style) - -Example: `add_class :some_class, borderStyle: :solid` adds a class with style `border-style: 'solid'` - -#### `run_on_client` - -same as `on_client` but no value is returned. Useful when the return value may be too complex to marshall and unmarshall using JSON. - -#### `reload_page` - -Shorthand for `mount` with no parameters. Useful if you need to reset the client within a spec. - -#### `size_window` - -Indicates the size of the browser window. The values can be given either symbolically or as two numbers (width and height). Predefined sizes are: - -+ `:small`: 480 x 320 -+ `:mobile` 640 x 480 -+ `:tablet` 960 x 64, -+ `:large` 1920 x 6000 -+ `:default` 1024 x 768 - -All of the above can be modified by providing the `:portrait` option as the first or second parameter. - -So for example the following are all equivalent: - -+ `size_window(:small, :portrait)` -+ `size_window(:portrait, :small)` -+ `size_window(320, 480)` - -#### `attributes_on_client` - -returns any `ActiveModel` attributes loaded on the client. HyperModel will normally begin a load cycle as soon as you access the attribute on the client. However it is sometimes useful to see what attributes have already been loaded. - -#### `insert_html` - -takes a string and inserts it into test page when it is mounted. Useful for testing code that is not dependent on Hyper Components. -For example an Opal library that adds some jQuery extensions. - -### Client Expectation Targets - -These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. - -For example: - -```ruby -it 'has built-in expectation targets' do - expect { RUBY_ENGINE }.on_client_to eq('opal') -end -``` - -The above expectation is short for saying: - -```ruby - result = on_client { RUBY_ENGINE } - expect(result).to eq('opal') -``` - -These methods have the following aliases to make your specs more readable: -+ `to_on_client` -+ `on_client_to_not` -+ `on_client_not_to` -+ `to_not_on_client` -+ `not_to_on_client` -+ `to_then` -+ `then_to_not` -+ `then_not_to` -+ `to_not_then` -+ `not_to_then` - -The `then` variants are useful to note that the spec involves a promise, but it does no explicit checking that the result comes from a promise. - -In addition the `with` method can be chained with the above methods to pass data to initialize local variables on the client: - -```ruby - it 'can pass values to the client using the with method' do - expect { foo * foo }.with(foo: 12).to_on_client eq(144) - end -``` - -By default HyperSpec will copy all local variables, memoized variables, and instance variables defined in a spec to the client. The specific variables can also be white listed and black listed. The `with` method overrides any white or black listed values. So for example if you prefer to use the more explicit `with` method to pass values to the client, you can add `client_option exclude_vars: true` in a `before(:all)` block in your spec helper. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. - -### Useful Debug Methods - -These methods are primarily designed to help debug code and specs. - -#### `c?` - -Shorthand for `on_client`, useful for entering expressions in the pry console, to investigate the state of the client. - -```ruby -pry:> c? { puts 'hello on the console' } # prints hello on the client --> nil -``` - -#### `to_js` - -Takes a block like `on_client` but rather than running the code on the client, simply returns the resulting code. This is useful for debugging obscure problems when the Opal compiler or some feature of -Hyperspec is suspected as the issue. - -#### `ppr` - -Takes a block like `on_client` and prints the result on the client console using JS console.log. Equivalent to doing - -```ruby - on_client do - begin - ... - end.tap { |r| `console.log(r)` } - end -``` - -This is useful when the result cannot be usefully returned to the server, -or when the result of interest is better looked at as the raw -javascript object. - -#### `debugger` - -This psuedo method can be inserted into any code executed on the client. It will cause the code to stop, and enter a *javascript* read-eval loop, within the debug console. - -Unfortunately ATM we do not have the technology to enter a *Ruby* read-eval loop at an arbitrary point on the client. - -> Note: due to a bug in the Opal compiler your code should not have `debugger` as the last expression in a method or a block. In this situation add any expression (such as nil) after the debugger statement. -```ruby -def foo - ... some code ... - debugger # this will fail with a compiler syntax error -end -``` - -#### `open_in_chrome` -By default specs are run with headless chrome, so there is no visible browser window. The `open_in_chrome` method will open a browser window, and load it with the current state. - -You can also run specs in a visible chrome window by setting the `DRIVER` environment variable to `chrome`. i.e. (`DRIVER=chrome bundle exec rspec ...`) - -#### `pause` -The method is typically not needed assuming you are using a multithreaded server like Puma. If for whatever reason the pry debug session is not multithreaded, *and* you want to try some kind of experiment on the javascript console, *and* those experiments make requests to the server, you may not get a response, because all threads are in use. - -You can resolve this by using the `pause` method in the debug session which will put the server debug session into a non-blocking loop. You can then experiment in the JS console, and when done release the pause by executing `go()` in the *javascript* debug console. diff --git a/docs/hyper-spec/04-using-with-rack.md b/docs/hyper-spec/04-using-with-rack.md deleted file mode 100644 index 7c5147f40..000000000 --- a/docs/hyper-spec/04-using-with-rack.md +++ /dev/null @@ -1,56 +0,0 @@ -# Using Hyperspec with Rack - -Hyperspec will run with Rails out of the box, but you can also use Hyperspec with any Rack application, with just a little more setup. For example here is a sample configuration setup with Sinatra: - -```ruby -# Gemfile -... - -gem "sinatra" -gem "rspec" -gem "pry" -gem "opal" -gem "opal-sprockets" -gem "rack" -gem "puma" -group :test do - # gem 'hyper-spec', '~> 1.0.alpha1.0' - # or to use edge: - gem 'hyper-spec', - git: 'git://github.com/hyperstack-org/hyperstack.git', - branch: 'edge', - glob: 'ruby/*/*.gemspec' -end -``` - -```ruby -# spec/spec_helper.rb - -require "bundler" -Bundler.require -ENV["RACK_ENV"] ||= "test" - -# require your application files as needed -require File.join(File.dirname(__FILE__), "..", "app.rb") - -# bring in needed support files -require "rspec" -require "rack/test" -require "hyper-spec/rack" - -# assumes your sinatra app is named app -Capybara.app = HyperSpecTestController.wrap(app: app) - -set :environment, :test -set :run, false -set :raise_errors, true -set :logging, false -``` - -### Details - -The interface between Hyperspec and your application environment is defined by the `HyperspecTestController` class. This file typically includes a set of helper methods from `HyperSpec::ControllerHelpers`, which can then be overridden to give whatever behavior your specific framework needs. Have a look at the `hyper-spec/rack.rb` and `hyper-spec/controller_helpers.rb` files in the Hyperspec gem directory. - -### Example - -A complete (but very simple) example is in this repos `ruby/examples/misc/sinatra_app` directory diff --git a/docs/hyper-spec/README.md b/docs/hyper-spec/README.md deleted file mode 100644 index 44dd165e3..000000000 --- a/docs/hyper-spec/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# HyperSpec - -## Adding client side testing to RSpec - -The `hyper-spec` gem supports the Hyperstack goals of programmer productivity and seamless web development by allowing testing to be done with minimal concern for the client-server interface. - -The `hyper-spec` gem adds functionality to the `rspec`, `capybara`, `timecop` and `pry` gems allowing you to do the following: - -+ write component and integration tests using the rspec syntax and helpers -+ write specs that run on both the client and server -+ evaluate client side ruby expressions from within specs and while using `pry` -+ share data between the client and server within your specs -+ control and synchronize time on the client and the server - -HyperSpec can be used standalone, but if used as part of a Hyperstack application it allows straight forward testing of Hyperstack Components and your ActiveRecord Models. - -So for example here is part of a simple unit test of a TodoIndex component: - -```ruby -it "will update the TodoIndex", js: true do - # mounts the TodoIndex component (client side) - mount 'TodoIndex' - # Todo is an ActiveRecord Model - # create a new Todo on the server (we could use FactoryBot of course) - todo_1 = Todo.create(title: 'this todo created on the server') - # verify that UI got updated - expect(find('.ToDoItem-Text').text).to eq todo_1.title - # verify that the count of Todos on the client side DB matches the server - expect { Todo.count }.on_client_to eq Todo.count - # now create another Todo on the client - new_todo_title = 'this todo created on the client' - # note that local variables are copied from the server to the client - on_client { Todo.create(title: new_todo_title) } - # the Todo should now be reflected on the server - expect(Todo.last.title).to eq new_todo_title -end -``` - -When using HyperSpec all the specs execute on the server side, but they may also interrogate the state of the UI as well as the state -of any of the client side objects. The specs can execute any valid Ruby code client side to create new test objects as well as do -white box testing. This keeps the logic of your specs in one place. From 38bead6c7f3ce6ca55d3a615d7b433fef0e8ca52 Mon Sep 17 00:00:00 2001 From: catmando Date: Sun, 14 Mar 2021 20:23:58 -0400 Subject: [PATCH 200/307] added hyperspec docs --- .../hyper-spec/01-installation.md | 58 +++ .../hyper-spec/02-tutorial.md | 122 +++++ .../hyper-spec/03-methods-and-features.md | 440 ++++++++++++++++++ .../hyper-spec/04-using-with-rack.md | 56 +++ .../development-workflow/hyper-spec/README.md | 41 ++ 5 files changed, 717 insertions(+) create mode 100644 docs/development-workflow/hyper-spec/01-installation.md create mode 100644 docs/development-workflow/hyper-spec/02-tutorial.md create mode 100644 docs/development-workflow/hyper-spec/03-methods-and-features.md create mode 100644 docs/development-workflow/hyper-spec/04-using-with-rack.md create mode 100644 docs/development-workflow/hyper-spec/README.md diff --git a/docs/development-workflow/hyper-spec/01-installation.md b/docs/development-workflow/hyper-spec/01-installation.md new file mode 100644 index 000000000..86deea4be --- /dev/null +++ b/docs/development-workflow/hyper-spec/01-installation.md @@ -0,0 +1,58 @@ +# HyperSpec Installation + +Add `gem 'hyper-spec'` to your Gemfile in the usual way. +Typically in a Rails app you will add this in the test section of your Gemfile: + +```ruby +group :test do + gem 'hyper-spec', '~> 1.0.alpha1.0' +end +``` + +Make sure to `bundle install`. + +> Note: if you want to use the unreleased edge branch your hyper-spec gem specification will be: +> +> ```ruby +> gem 'hyper-spec', +> git: 'git://github.com/hyperstack-org/hyperstack.git', +> branch: 'edge', +> glob: 'ruby/*/*.gemspec' +> ``` + +HyperSpec is integrated with the `pry` gem for debugging, so it is recommended to add the `pry` gem as well. + +HyperSpec will also use the `timecop` gem if present to allow you to control and synchronize time on the server and the client. + +A typical spec_helper file when using HyperSpec will look like this: + +```ruby +# spec_helper.rb +require 'hyper-spec' +require 'pry' # optional + +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path('../test_app/config/environment', __FILE__) + +require 'rspec/rails' +require 'timecop' # optional + +# any other rspec configuration you need +# note HyperSpec will include chrome driver for providing the client +# run time environment +``` + +To load the webdriver and client environment your spec should have the +`:js` flag set: + +```ruby +# the js flag can be set on the entire group of specs, or a context +describe 'some hyper-specs', :js do + ... +end + +# or for an individual spec + it 'an individual hyper-spec', :js do + ... + end +``` diff --git a/docs/development-workflow/hyper-spec/02-tutorial.md b/docs/development-workflow/hyper-spec/02-tutorial.md new file mode 100644 index 000000000..d988dc86c --- /dev/null +++ b/docs/development-workflow/hyper-spec/02-tutorial.md @@ -0,0 +1,122 @@ +# Tutorial + +For this quick tutorial lets assume you have an existing Rails app that +already uses RSpec to which you have added a first Hyperstack component to +try things out. + +For your trial, you have created a very simple component that shows +the number of orders shipped by your companies website: + +```ruby +class OrdersShipped < HyperComponent + def format_number(number) + number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse + end + + render(DIV, class: 'orders-shipped') do + format_number Order.shipped.count + end +end +``` + +> Note that styling can be taken care of in the usual way by +> providing styles for the `orders-shipped` css class. All we care +> about here is the *function* of the component. + +Meanwhile `Order` is an ActiveRecord Model that would look something like this: + +```ruby +class Order < ApplicationRecord + ... + scope :shipped, -> () { where(status: :shipped) } + ... +end +``` + +> Note that when using ActiveRecord models in your specs you will +> need to add the appropriate database setup and cleaner methods like you would +> for any specs used with ActiveRecord. We assume here that as each +> spec starts there are no records in the database + +The `OrdersShipped` component can be mounted on any page of your site, +and assuming the proper policy permissions are provided it will +show the total orders shipped, and will dynamically increase in +realtime. + +A partial spec for this component might look like this: + +```ruby +require 'spec_helper' + +describe 'OrdersShipped', :js do + it 'dynamically displays the orders shipped' do + mount 'OrdersShipped' + expect(find('div.orders-shipped')).to have_content(0) + Order.create(status: :shipped) + expect(find('div.orders-shipped')).to have_content(1) + Order.last.destroy + expect(find('div.orders-shipped')).to have_content(0) + end + + it '#format method' do + on_client { @comp = OrdersShipped.new } + ['1,234,567', '123', '1,234'].each do |n| + expect { @comp.format_number(n.gsub(',','').to_i) } + .on_client_to eq(n) + end + end +end +``` + +If you are familiar with Capybara then the first spec should +look similar to an integration spec. The difference is instead +of visiting a page, we `mount` the `OrdersShipped` component on a blank page +that hyper-spec will set up for us. This lets us unit test +components outside of any application specific view logic. + +> Note that like Capybara we indicate that a client environment should +> be set up by adding the :js tag. + +Once mounted we can use Capybara finders and matchers to check +if our content is as expected. Because we are running on the server +we can easily add and delete orders, and check the response on the UI. + +The second spec shows how we can do some white box unit testing of our +component. Instead of mounting the component we just create a new +instance which will be invisible since it was not mounted. For this we +use the `on_client` method. + +The `on_client` method takes a block, and will +compile that block using +Opal, and execute it on the client. In this case we simply create a +new `OrderShipped` instance, and assign it to an instance variable, which as you +will see will continue to be available to us later in the spec. + +> Note, if you are an RSpec purist, you would probably prefer to see +> something like `let` be used here instead of an instance variable. Shall we +> say its on the todo list. + +Now that we have our test component setup we can test its `format_number` +method. To do this we put the test expression in a block followed by +`on_client_to`. Again the block will be compiled using Opal, executed on +the client, and the result will be returned to the expectation. + +Notice that the server side variable `n` can be read (but not written) within +the client block. All local variables, memoized variables, and instance variables can +can be read in the client block as long as they represent objects that can be +sensibly marshalled and unmarshalled. + +This has covered the basics of Hyperspec - in summary: + ++ The `js` tag indicates the spec will be using a client environment. ++ `mount`: Mount a component on a blank page. This replaces the `visit` method +for unit testing components. ++ `on_client`: Execute Ruby code on the client (and return the result). ++ `on_client_to`: Execute the expectation block on the client, and then check +the expectation (on the server.) ++ Instance variables retain their values between client execution blocks. ++ All variables accessible to the spec are copied to the client if possible. + +There are many other features such as dealing with promises, passing data to +and from a mounted component, using the `Timecop` gem, and working with a `pry` +session. So read on. diff --git a/docs/development-workflow/hyper-spec/03-methods-and-features.md b/docs/development-workflow/hyper-spec/03-methods-and-features.md new file mode 100644 index 000000000..8fec6d0c6 --- /dev/null +++ b/docs/development-workflow/hyper-spec/03-methods-and-features.md @@ -0,0 +1,440 @@ +# HyperSpec Methods and Features + +### Expectation Helpers + +These can be used any where within your specs: + ++ [`on_client`](#the-on_client-method) - executes code on the client ++ [`isomorphic`](#the-isomorphic-method) - executes code on the client *and* the server ++ [`mount`](#mounting-components) - mounts a hyperstack component in an empty window ++ [`before_mount`](#before_mount) - specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client` ++ [`insert_html`](#insert_html) - insert some html into a page ++ [`client_options`](#client-initialization-options) - allows options to be specified globally ++ [`run_on_client`](#run_on_client) - same as `on_client` but no value is returned ++ [`reload_page`](#reload_page) - resets the page environment ++ [`add_class`](#add_class) - adds a CSS class ++ [`size_window`](#size_window) - specifies how big the client window should be ++ [`attributes_on_client`](#attributes_on_client) - returns any ActiveModel attributes loaded on the client + +These methods are used after mounting a component to retrieve +events sent outwards from the component: + ++ [`callback_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`last_callback_for`](#retrieving-event-data-from-the-mounted-component) ++ [`clear_callback_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`event_history_for`](#retrieving-event-data-from-the-mounted-component) ++ [`last_event_for`](#retrieving-event-data-from-the-mounted-component) ++ [`clear_event_history_for`](#retrieving-event-data-from-the-mounted-component) + +### Expectation Targets + +These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. + ++ [`on_client_to`](#client-expectation-targets), [`to_on_client_not`](#client-expectation-targets) - the expression will be evaluated on the client, and matched on the server. + +These methods have the following aliases to make your specs more readable: ++ [`to_on_client`](#client-expectation-targets) ++ [`on_client_to_not`](#client-expectation-targets) ++ [`on_client_not_to`](#client-expectation-targets) ++ [`to_not_on_client`](#client-expectation-targets) ++ [`not_to_on_client`](#client-expectation-targets) ++ [`to_then`](#client-expectation-targets) ++ [`then_to_not`](#client-expectation-targets) ++ [`then_not_to`](#client-expectation-targets) ++ [`to_not_then`](#client-expectation-targets) ++ [`not_to_then`](#client-expectation-targets) + +in addition ++ [`with`](#client-expectation-targets) - can be chained with the above methods to pass data to initialize local variables on the client + +### Other Debugging Aids + +The following methods are used primarly at a debug break point, most require you use binding.pry as your debugger: + ++ [`to_js`](#to_js) - returns the ruby code compiled to JS. ++ [`c?`](#c?) - alias for `on_client`. ++ [`ppr`](#ppr) - print the results of the ruby expression on the client console. ++ [`debugger`](#debugger) - Sets a debug breakpoint on code running on the client. ++ [`open_in_chrome`](#open_in_chrome) - Opens a chrome browser that will load the current state. ++ [`pause`](#pause) - Halts execution on the server without blocking I/O. + +### Available Webdrivers + +HyperSpec comes integrated with Chrome and Chrome headless webdrivers. The default configuration will run using Chrome headless. To see what is going on set the `DRIVER` environment variable to `chrome` +```bash +DRIVER=chrome bundle exec rspec +``` + +### Timecop Integration + +You can use the [`timecop` gem](https://github.com/travisjeffery/timecop) to control the flow of time within your specs. Hyperspec will coordinate things with the client so the time on the client is kept in sync with the time on the server. So for example if you use Timecop to advance time 1 day on the server, time on the browser will also advance by one day. + +See the [Client Initialization Options](#client-initialization-options) section for how to control the client time zone, and clock resolution. + +### The `no_reset` flag + +By default the client environment will be reinitialized at the beginning of every spec. If this is not needed you can speed things up by adding the `no_reset` flag to a block of specs. + +# Details + +### The `on_client` method + +The on_client method takes a block. The ruby code inside the block will be executed on the client, and the result will be returned. + +```ruby + it 'will print a message on the client' do + on_client do + puts 'hey I am running here on the client!' + end + end +``` + +If the block returns a promise Hyperspec will wait for the promise to be resolved (or rejected) before returning. For example: + +```ruby + it 'waits for a promise' do + start_time = Time.now + result = on_client do + promise = Promise.new + after(10.seconds) { promise.resolve('done!') } + promise + end + expect(result).to eq('done!') + expect(Time.now-start_time).to be >= 10.seconds + end +``` +> HyperSpec will do its best to reconstruct the result back on the server in some sensible way. Occasionally it just doesn't work, in which case you can end the block with a `nil` or some other simple expression, or use the `run_on_client` method, which does not return the result. + +### Accessing variables on the client + +It is often useful to pass variables from the spec to the client. Hyperspec will copy all your local variables, memoized variables, and instance variables known at the time the `on_client` block is compiled to the client.
+```ruby + let!(memoized) { 'a memoized variable' } + it 'will pass variables to the client' do + local = 'a local variable' + @instance = 'an instance variable' + result = on_client { [memoized, local, @instance] } + expect(result).to eq [memoized, local, @instance] + end +``` +> Note that memoized variables are not initialized until first +accessed, so you probably want to use the let! method unless you +are sure you are accessing the memoized value before sending it to the client. + +The value of instance variables initialized on the client are preserved +across blocks executed on the client. For example: +```ruby + it 'remembers instance variables' do + on_client { @total = 0 } + 10.times do |i| + # note how we are passing i in + on_client { @total += i } + end + result = on_client { @total } + expect(result).to eq(10 * 11 / 2) + end +``` + +> Be especially careful of this when using the [`no_reset` flag](#the-no_reset-flag) as instance variables will retain their values between each spec in this mode. + +#### White and Black Listing Variables + +By default all local variables, memoized variables, and instance variables in scope in the spec will be copied to the client. This can be controlled through the `include_vars` and `exclude_vars` [client options](#client-initialization-options). + +`include_vars` can be set to ++ an array of symbols: only those vars will be copied, ++ a single symbol: only that var will be copied, ++ any other truthy value: all vars will be copied (the default) ++ or nil, false, or an empty array: no vars will be copied. + +`exclude_vars` can be set to ++ an array of symbols - those vars will **not** be copied, ++ a single symbol - only that var will be excluded, ++ any other truthy value - no vars will be copied, ++ or nil, false, or an empty array - all vars will be copied (the default). + +Examples: + +```Ruby + # don't copy vars at all. + client_option exclude_vars: true + # only copy var1 and the instance var @var2 + client_option include_vars: [:var1, :@var2] + # only exclude foo_var + client_option exclude_vars: :foo_var +``` + +Note that the exclude_vars list will take precedence over the include_vars list. + +The exclude/include lists can be overridden on an individual call to on_client by providing a hash of names and values to on_client: + +```ruby + result = on_client(var: 12) { var * var } + expect(result).to eq(144) +``` + +You can do the same thing on expectations using the `with` method - See [Client Expectation Targets](#client-expectation-targets). + + +### The `isomorphic` method + +The `isomorphic` method works the same as `on_client` but in addition it also executes the same block on the server. It is especially useful when doing some testing of +ActiveRecord models, where you might want to modify the behavior of the model on server and the client. + +```ruby + it 'can run code the same everywhere!' do + isomorphic do + def factorial(x) + x.zero? ? 1 : x * factorial(x - 1) + end + end + + on_the_client = on_client { factorial(7) } + on_the_server = factorial(7) + expect(on_the_client).to eq(on_the_server) + end +``` + +### Client Initialization Options + +The first time a spec runs code on the client, it has to initialize a browser context. You can use the `client_options` (aka `client_option`) method to specify the following options when the page is loaded. + ++ `time_zone` - browsers always run in the local time zone, if you want to force the browser to act as if its in a different zone, you can use the time_zone option, and provide any valid zone that the rails `in_time_zone` method will accept.
+Example: `client_option time_zone: 'Hawaii'` ++ `clock_resolution`: Indicates the resolution that the simulated clock will run at on the client, when using the TimeCop gem. The default value is 20 (milliseconds). ++ `include_vars`: white list of all vars to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. ++ `exclude_vars`: black list of all vars not to be copied to the client. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. ++ `render_on`: `:client_only` (default), `:server_only`, or `:both` +Hyperstack components can be prerendered on the server. The `render_on` option controls this feature. For example `server_only` is useful to insure components are properly prerendered. *See the `mount` method [below](#mounting-components) for more details on rendering components* ++ `no_wait`: After the page is loaded the system will by default wait until all javascript requests to the server complete before proceeding. Specifying `no_wait: true` will skip this. ++ `javascript`: The javascript asset to load when mounting the component. By default it will be `application` (.js is assumed). Note that the standard Hyperstack configuration will compile all the client side Ruby assets as well as javascript packages into the `application.js` file, so the default will work fine. ++ `style_sheet`: The style sheet asset to load when mounting the component. By default it will be `application` (.css is assumed). ++ `controller` - **(expert zone!)** specify a controller that will be used to mount the +component. By default hyper-spec will build a controller and route to handle the request from the client to mount the component. + +Any other options not listed above will be passed along to the Rail's controller `render` method. So for example you could specify some other specific layout using `client_option layout: 'special_layout'` + +Note that this method can be used in the `before(:each)` block of a spec context to provide options for all the specs in the block. + +### Mounting Components + +The `mount` method is used to render a component on a page: + +```ruby + it 'can display a component for me' do + mount 'SayHello', name: 'Lannar' do + class SayHello < HyperComponent + param :name + render(DIV) do + "Hello #{name}!" + end + end + end + + expect(page).to have_content('Hello Lannar') + end +``` + +The `mount` method has a few options. In it's simplest form you specify just the name of the component that is already defined in your hyperstack code and it will be mounted. + +You can add parameters that will be passed to the component as in the above example. As the above example also shows you can also define code within the block. This is just shorthand for defining the code before hand using `on_client`. The code does not have to be the component being mounted, but might be just some logic to help with the test. + +In addition `mount` can take any of the options provided to `client_options` (see above.) To provide these options, you must provide a (possibly) empty params hash. For example: +```ruby +mount 'MyComponent', {... params ... }, {... opts ... } +``` + +### Retrieving Event Data From the Mounted Component + +Components *receive* parameters, and may send callbacks and events back out. To test if a component has sent the appropriate data you can use the following methods: + ++ `callback_history_for` ++ `last_callback_for` ++ `clear_callback_history_for` ++ `event_history_for` ++ `last_event_for` ++ `clear_event_history_for` + +```ruby + it 'can check on a clients events and callbacks' do + mount 'BigTalker' do + class BigTalker < HyperComponent + fires :i_was_clicked + param :call_me_back, type: Proc + + before_mount { @click_counter = 0 } + + render(DIV) do + BUTTON { 'click me' }.on(:click) do + @click_counter += 1 + i_was_clicked! + call_me_back.call(@click_counter) + end + end + end + end + 3.times do + find('button').click + end + # the history is an array, one element for each item in the history + expect(event_history_for(:i_was_clicked).length).to eq(3) + # each item in the array is itself an array of the arguments + expect(last_call_back_for(:call_me_back)).to eq([3]) + # clearing the history resets the array to empty + clear_event_history_for(:i_was_clicked) + expect(event_history_for(:i_was_clicked).length).to eq(0) + end +``` + +> Note that you must declare the params as type `Proc`, or use +the `fires` method to declare an event for the history mechanism to work. + +### Other Helpers + +#### `before_mount` + +Specifies a block of code to be executed before the first call to `mount`, `isomorphic` or `on_client`. This is primarly useful to add to an rspec `before(:each)` block containing common client code needed by all the specs in the context. + +> Unlike `mount`, `isomorphic` and `on_client`, `before_mount` does not load the client page, but will wait for the first of the other methods to be called. + +#### `add_class` + +Adds a CSS class. The first parameter is the name of the class, and the second is a hash of styles, represented in the React [style format.](https://reactjs.org/docs/dom-elements.html#style) + +Example: `add_class :some_class, borderStyle: :solid` adds a class with style `border-style: 'solid'` + +#### `run_on_client` + +same as `on_client` but no value is returned. Useful when the return value may be too complex to marshall and unmarshall using JSON. + +#### `reload_page` + +Shorthand for `mount` with no parameters. Useful if you need to reset the client within a spec. + +#### `size_window` + +Indicates the size of the browser window. The values can be given either symbolically or as two numbers (width and height). Predefined sizes are: + ++ `:small`: 480 x 320 ++ `:mobile` 640 x 480 ++ `:tablet` 960 x 64, ++ `:large` 1920 x 6000 ++ `:default` 1024 x 768 + +All of the above can be modified by providing the `:portrait` option as the first or second parameter. + +So for example the following are all equivalent: + ++ `size_window(:small, :portrait)` ++ `size_window(:portrait, :small)` ++ `size_window(320, 480)` + +#### `attributes_on_client` + +returns any `ActiveModel` attributes loaded on the client. HyperModel will normally begin a load cycle as soon as you access the attribute on the client. However it is sometimes useful to see what attributes have already been loaded. + +#### `insert_html` + +takes a string and inserts it into test page when it is mounted. Useful for testing code that is not dependent on Hyper Components. +For example an Opal library that adds some jQuery extensions. + +### Client Expectation Targets + +These can be used within expectations replacing the `to` and `not_to` methods. The expectation expression must be inclosed in a block. + +For example: + +```ruby +it 'has built-in expectation targets' do + expect { RUBY_ENGINE }.on_client_to eq('opal') +end +``` + +The above expectation is short for saying: + +```ruby + result = on_client { RUBY_ENGINE } + expect(result).to eq('opal') +``` + +These methods have the following aliases to make your specs more readable: ++ `to_on_client` ++ `on_client_to_not` ++ `on_client_not_to` ++ `to_not_on_client` ++ `not_to_on_client` ++ `to_then` ++ `then_to_not` ++ `then_not_to` ++ `to_not_then` ++ `not_to_then` + +The `then` variants are useful to note that the spec involves a promise, but it does no explicit checking that the result comes from a promise. + +In addition the `with` method can be chained with the above methods to pass data to initialize local variables on the client: + +```ruby + it 'can pass values to the client using the with method' do + expect { foo * foo }.with(foo: 12).to_on_client eq(144) + end +``` + +By default HyperSpec will copy all local variables, memoized variables, and instance variables defined in a spec to the client. The specific variables can also be white listed and black listed. The `with` method overrides any white or black listed values. So for example if you prefer to use the more explicit `with` method to pass values to the client, you can add `client_option exclude_vars: true` in a `before(:all)` block in your spec helper. See [Accessing Variables on the Client](#accessing-variables-on-the-client) for details. + +### Useful Debug Methods + +These methods are primarily designed to help debug code and specs. + +#### `c?` + +Shorthand for `on_client`, useful for entering expressions in the pry console, to investigate the state of the client. + +```ruby +pry:> c? { puts 'hello on the console' } # prints hello on the client +-> nil +``` + +#### `to_js` + +Takes a block like `on_client` but rather than running the code on the client, simply returns the resulting code. This is useful for debugging obscure problems when the Opal compiler or some feature of +Hyperspec is suspected as the issue. + +#### `ppr` + +Takes a block like `on_client` and prints the result on the client console using JS console.log. Equivalent to doing + +```ruby + on_client do + begin + ... + end.tap { |r| `console.log(r)` } + end +``` + +This is useful when the result cannot be usefully returned to the server, +or when the result of interest is better looked at as the raw +javascript object. + +#### `debugger` + +This psuedo method can be inserted into any code executed on the client. It will cause the code to stop, and enter a *javascript* read-eval loop, within the debug console. + +Unfortunately ATM we do not have the technology to enter a *Ruby* read-eval loop at an arbitrary point on the client. + +> Note: due to a bug in the Opal compiler your code should not have `debugger` as the last expression in a method or a block. In this situation add any expression (such as nil) after the debugger statement. +```ruby +def foo + ... some code ... + debugger # this will fail with a compiler syntax error +end +``` + +#### `open_in_chrome` +By default specs are run with headless chrome, so there is no visible browser window. The `open_in_chrome` method will open a browser window, and load it with the current state. + +You can also run specs in a visible chrome window by setting the `DRIVER` environment variable to `chrome`. i.e. (`DRIVER=chrome bundle exec rspec ...`) + +#### `pause` +The method is typically not needed assuming you are using a multithreaded server like Puma. If for whatever reason the pry debug session is not multithreaded, *and* you want to try some kind of experiment on the javascript console, *and* those experiments make requests to the server, you may not get a response, because all threads are in use. + +You can resolve this by using the `pause` method in the debug session which will put the server debug session into a non-blocking loop. You can then experiment in the JS console, and when done release the pause by executing `go()` in the *javascript* debug console. diff --git a/docs/development-workflow/hyper-spec/04-using-with-rack.md b/docs/development-workflow/hyper-spec/04-using-with-rack.md new file mode 100644 index 000000000..7c5147f40 --- /dev/null +++ b/docs/development-workflow/hyper-spec/04-using-with-rack.md @@ -0,0 +1,56 @@ +# Using Hyperspec with Rack + +Hyperspec will run with Rails out of the box, but you can also use Hyperspec with any Rack application, with just a little more setup. For example here is a sample configuration setup with Sinatra: + +```ruby +# Gemfile +... + +gem "sinatra" +gem "rspec" +gem "pry" +gem "opal" +gem "opal-sprockets" +gem "rack" +gem "puma" +group :test do + # gem 'hyper-spec', '~> 1.0.alpha1.0' + # or to use edge: + gem 'hyper-spec', + git: 'git://github.com/hyperstack-org/hyperstack.git', + branch: 'edge', + glob: 'ruby/*/*.gemspec' +end +``` + +```ruby +# spec/spec_helper.rb + +require "bundler" +Bundler.require +ENV["RACK_ENV"] ||= "test" + +# require your application files as needed +require File.join(File.dirname(__FILE__), "..", "app.rb") + +# bring in needed support files +require "rspec" +require "rack/test" +require "hyper-spec/rack" + +# assumes your sinatra app is named app +Capybara.app = HyperSpecTestController.wrap(app: app) + +set :environment, :test +set :run, false +set :raise_errors, true +set :logging, false +``` + +### Details + +The interface between Hyperspec and your application environment is defined by the `HyperspecTestController` class. This file typically includes a set of helper methods from `HyperSpec::ControllerHelpers`, which can then be overridden to give whatever behavior your specific framework needs. Have a look at the `hyper-spec/rack.rb` and `hyper-spec/controller_helpers.rb` files in the Hyperspec gem directory. + +### Example + +A complete (but very simple) example is in this repos `ruby/examples/misc/sinatra_app` directory diff --git a/docs/development-workflow/hyper-spec/README.md b/docs/development-workflow/hyper-spec/README.md new file mode 100644 index 000000000..44dd165e3 --- /dev/null +++ b/docs/development-workflow/hyper-spec/README.md @@ -0,0 +1,41 @@ +# HyperSpec + +## Adding client side testing to RSpec + +The `hyper-spec` gem supports the Hyperstack goals of programmer productivity and seamless web development by allowing testing to be done with minimal concern for the client-server interface. + +The `hyper-spec` gem adds functionality to the `rspec`, `capybara`, `timecop` and `pry` gems allowing you to do the following: + ++ write component and integration tests using the rspec syntax and helpers ++ write specs that run on both the client and server ++ evaluate client side ruby expressions from within specs and while using `pry` ++ share data between the client and server within your specs ++ control and synchronize time on the client and the server + +HyperSpec can be used standalone, but if used as part of a Hyperstack application it allows straight forward testing of Hyperstack Components and your ActiveRecord Models. + +So for example here is part of a simple unit test of a TodoIndex component: + +```ruby +it "will update the TodoIndex", js: true do + # mounts the TodoIndex component (client side) + mount 'TodoIndex' + # Todo is an ActiveRecord Model + # create a new Todo on the server (we could use FactoryBot of course) + todo_1 = Todo.create(title: 'this todo created on the server') + # verify that UI got updated + expect(find('.ToDoItem-Text').text).to eq todo_1.title + # verify that the count of Todos on the client side DB matches the server + expect { Todo.count }.on_client_to eq Todo.count + # now create another Todo on the client + new_todo_title = 'this todo created on the client' + # note that local variables are copied from the server to the client + on_client { Todo.create(title: new_todo_title) } + # the Todo should now be reflected on the server + expect(Todo.last.title).to eq new_todo_title +end +``` + +When using HyperSpec all the specs execute on the server side, but they may also interrogate the state of the UI as well as the state +of any of the client side objects. The specs can execute any valid Ruby code client side to create new test objects as well as do +white box testing. This keeps the logic of your specs in one place. From aa6cfdd6c887e6dc02f414245bf0515a0d6aafe8 Mon Sep 17 00:00:00 2001 From: catmando Date: Mon, 15 Mar 2021 23:11:13 -0400 Subject: [PATCH 201/307] closes #369 closes #370 closes #368 --- docs/client-dsl/README.md | 36 +++++++++--- docs/client-dsl/components.md | 55 ++++++++++++++++--- docs/client-dsl/html-css.md | 48 ++++++++++++++-- docs/installation/man-installation.md | 31 ++--------- .../lib/hyperstack/internal/component/tags.rb | 1 + .../spec/client_features/component_spec.rb | 45 ++++++++++++++- ruby/rails-hyperstack/.ruby-version | 2 +- ruby/rails-hyperstack/Rakefile | 4 +- .../hyperstack/install_generator.rb | 22 ++++---- .../install/hyperstack_generator.rb | 2 +- .../rails-hyperstack/rails-hyperstack.gemspec | 4 +- 11 files changed, 184 insertions(+), 66 deletions(-) diff --git a/docs/client-dsl/README.md b/docs/client-dsl/README.md index c4c706201..f726f40bd 100644 --- a/docs/client-dsl/README.md +++ b/docs/client-dsl/README.md @@ -1,19 +1,37 @@ # Client DSL -## HTML DSL and Hyperstack Component classes +## Hyperstack Component Classes and DSL -A key design goal of the DSL \(Domain Specific Language\) is to make it work seamlessly with the rest of Ruby and easy to work with HTML elements and Components. Additionally, the DSL provides an abstraction layer between your code and the underlying \(fast moving\) technology stack. Hyperstack always uses the very latest versions of React and React Router yet our DSL does not change often. We believe that a stable DSL abstraction is an advantage. +Your Hyperstack Application is built from a series of *Components* which are Ruby Classes that display portions of the UI. Hyperstack Components are implemented using [React](https://reactjs.org/), and can interoperate with existing React components and libraries. Here is a simple example that displays a ticking clock: -This documentation will cover the following core concepts: +```ruby +# Components inherit from the HyperComponent base class +# which supplies the DSL to translate from Ruby into React +# function calls +class Clock < HyperComponent + # before_mount is an example of a life cycle method + before_mount { every(1.second) { mutate @current_time = Time.now } } + # every component has a render block which describes what will be + # drawn on the UI + render do + # conponents can render other components or primitive HTML or SVG + # tags. Components also use their state to determine what to render, + # in this case the @current_time instance variable + DIV { @current_time.strftime("%m/%d/%Y %I:%M:%S") + end +end +``` -+ [HTML & CSS DSL](html-css.md) which provided Ruby implementations of all of the HTML and CSS elements +This documentation will cover the following core concepts, many of which +are touched on in the above simple example: + ++ [HTML & CSS DSL](html-css.md) which provides Ruby implementations of all of the HTML and CSS elements + [Component DSL](components.md) is a Ruby DSL which wraps ReactJS Components + [Lifecycle Methods](lifecycle-methods.md) are methods which are invoked before, during and after rendering -+ [State](state.md) governs all rendering in ReactJS -+ [Event Handlers](event-handlers.md) allow any HTML element or Component can respond to an event -+ [JavaScript Components](javascript-components.md) for the full universe of JS libraries in your Ruby code ++ [State](state.md) - components rerender as needed when state changes. ++ [Event Handlers](event-handlers.md) allow any HTML element or Component to respond to an event, plus custom events can be described. ++ [JavaScript Components](javascript-components.md) access to the full universe of JS libraries in your Ruby code + [Client-side Routing](hyper-router.md) a Ruby DSL which wraps ReactRouter + [Stores](hyper-store.md) for application level state and Component communication -+ [Elements and Rendering](elements-and-rendering.md) which are seldom used but useful to know ++ [Elements and Rendering](elements-and-rendering.md) details of the underlying mechanisms. + [Further Reading](further-reading.md) on React and Opal - diff --git a/docs/client-dsl/components.md b/docs/client-dsl/components.md index 8b28ce662..2e4fd4fe3 100644 --- a/docs/client-dsl/components.md +++ b/docs/client-dsl/components.md @@ -1,10 +1,10 @@ # Hyperstack Component DSL -Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface. +The Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface. The DSL has the following major areas: -* The `Hyperstack::Component` mixin or your own `HyperComponent` class +* The `HyperComponent` class * HTML DSL elements * Component Lifecycle Methods \(`before_mount`, `render`, `after_mount`, `after_update`, `after_error`\) * The `param` and `render` methods @@ -13,18 +13,24 @@ The DSL has the following major areas: ## HyperComponent -Hyperstack Components classes include the `Hyperstack::Component` mixin or \(for ease of use\) are a subclass of a `HyperComponent` class which includes the mixin: +By convention your Hyperstack Components will inherit from the `HyperComponent` class, which at a minimum looks like this: ```ruby class HyperComponent include Hyperstack::Component end +# and is used like this: + class AnotherComponent < HyperComponent end ``` -At a minimum every component class must define a `render` method which returns **one single** child element. That child may in turn have an arbitrarily deep structure. +Having an Application wide HyperComponent class allows you to modify component behavior on an application basis, similar to the way Rails uses `ApplicationRecord` and `ApplicationController` classes. + +## The `render` Callback + +At a minimum every component class must define a `render` callback which returns one or more child elements. Those children may in turn have an arbitrarily deep structure. ```ruby class Component < HyperComponent @@ -34,7 +40,7 @@ class Component < HyperComponent end ``` -You may also include the top level element to be rendered: +To save a little typing you can also include the top level element to be rendered: ```ruby class Component < HyperComponent @@ -130,9 +136,8 @@ Parent { Child() } ```ruby param :items render do - # notice how the items param is accessed in CamelCase (to indicate that it is read-only) items.each do |item| - PARA do + P do item[:text] end end @@ -230,6 +235,41 @@ class IndentEachLine < HyperComponent end ``` +### Rendering Multiple Values and the FRAGMENT component + +A render block may generate multiple values. React assumes when a Component generates multiple items, the item order and quantity may +change over time and so will give a warning unless each element has a key: + +```ruby +class ListItems < HyperComponent + render do + LI(key: 1) { 'item 1' } + LI(key: 2) { 'item 2' } + LI(key: 3) { 'item 3' } + end +end + +# somewhere else: + UL do + ListItems() + end +``` + +If you are sure that the order and number of elements will not change over time you may wrap the items in the FRAGMENT pseudo component: + +```ruby +class ListItems < HyperComponent + render(FRAGMENT) do + LI { 'item 1' } + LI { 'item 2' } + LI { 'item 3' } + end +end +``` + +The only param that FRAGMENT may take is a key, which is useful if there will be multiple fragments being merged, at some higher level. + + ### Data Flow In React, data flows from owner to owned component through the params as discussed above. This is effectively one-way data binding: owners bind their owned component's param to some value the owner has computed based on its `params` or `state`. Since this process happens recursively, data changes are automatically reflected everywhere they are used. @@ -424,4 +464,3 @@ end ``` Note: `collect_other_params_as` builds a hash, so you can merge other data in or even delete elements out as needed. - diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index eb14c5d06..1f1e8b91d 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -47,21 +47,57 @@ TABLE(class: 'ui celled table') do end ``` -The following HTML elements are available: +The following HTML and SVG elements are available: + +
+HTML Tags ```markup -A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON CANVAS CAPTION CITE CODE COL COLGROUP DATA DATALIST DD DEL DETAILS DFN DIALOG DIV DL DT EM EMBED FIELDSET FIGCAPTION FIGURE FOOTER FORM H1 H2 H3 H4 H5 H6 HEAD HEADER HR HTML I IFRAME IMG INPUT INS KBD KEYGEN LABEL LEGEND LI LINK MAIN MAP MARK MENU MENUITEM META METER NAV NOSCRIPT OBJECT OL OPTGROUP OPTION OUTPUT P PARAM PICTURE PRE PROGRESS Q RP RT RUBY S SAMP SCRIPT SECTION SELECT SMALL SOURCE SPAN STRONG STYLE SUB SUMMARY SUP TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK U UL VAR VIDEO WBR +A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO +B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON +CANVAS CAPTION CITE CODE COL COLGROUP +DATA DATALIST DD DEL DETAILS DFN DIALOG DIV DL DT +EM EMBED +FIELDSET FIGCAPTION FIGURE FOOTER FORM +H1 H2 H3 H4 H5 H6 HEAD HEADER HR HTML +I IFRAME IMG INPUT INS +KBD KEYGEN +LABEL LEGEND LI LINK +MAIN MAP MARK MENU MENUITEM META METER +NAV NOSCRIPT +OBJECT OL OPTGROUP OPTION OUTPUT +P PARAM PICTURE PRE PROGRESS +Q +RP RT RUBY +S SAMP SCRIPT SECTION SELECT SMALL SOURCE SPAN STRONG STYLE SUB SUMMARY SUP +TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK +U UL +VAR VIDEO +WBR ``` +
-And also the SVG elements: +
+SVG Tags ```markup -CIRCLE CLIPPATH DEFS ELLIPSE G LINE LINEARGRADIENT MASK PATH PATTERN POLYGON POLYLINE RADIALGRADIENT RECT STOP SVG TEXT TSPAN +CIRCLE CLIPPATH +DEFS +ELLIPSE +G +LINE LINEARGRADIENT +MASK +PATH PATTERN POLYGON POLYLINE +RADIALGRADIENT RECT +STOP +SVG +TEXT TSPAN ``` +
### HTML parameters -You can pass any expected parameter to a HTML element: +You can pass any expected parameter to a HTML or SVG element: ```ruby A(href: '/') { 'Click me' } # Click me @@ -82,7 +118,7 @@ P(class: :bright) { } P(class: [:bright, :blue]) { } # class='bright blue' ``` -For `style` you need to pass a hash: +For `style` you need to pass a hash using the [React style conventions](https://reactjs.org/docs/dom-elements.html#style): ```ruby PARA(style: { display: item[:some_property] == "some state" ? :block : :none }) diff --git a/docs/installation/man-installation.md b/docs/installation/man-installation.md index f2772802d..4b1056520 100644 --- a/docs/installation/man-installation.md +++ b/docs/installation/man-installation.md @@ -2,40 +2,20 @@ You can install Hyperstack either -* using a template - best for new applications \(not working for Rails 6\) yet; -* using a Rails generator - best for existing Rails apps; +* using a Rails generator, * or manually walking through the detailed installation instructions below. -Even if you use the template or generator, later reading through the detailed installation instructions can be helpful to understand how the system fits together and the generators work. +Even if you use the generator later reading through the detailed installation instructions can be helpful to understand how the system fits together and the generators work. ## Pre-Requisites -#### - Rails 5.x [Install Instructions](http://railsinstaller.org/en) +#### - Rails >= 5.x [Install Instructions](http://railsinstaller.org/en) -And for a full system as setup by the template or install generator you will need +And for a full system as setup generator you will need #### - Yarn [Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) -## Installing using the template - -This template will create a **new** Rails app with Webpacker from Hyperstack edge branch. This is the easiest way to get started. - -### Run the template - -Simply run the command below to create a new Rails app with Hyperstack all configured: - -```text -rails new MyApp -T -m https://rawgit.com/hyperstack-org/hyperstack/edge/install/rails-webpacker.rb -``` - -> Note: The -T flag will not install minitest directories, leaving room for Rspec and Hyperspec. See the HyperSpec readme under "Tools" for more info. - -### Start the Rails app - -* `foreman start` to start Rails and the Hotloader -* Navigate to `http://localhost:5000/` - -## Installing in an Existing Rails App +## Installing Using the Generator If you have an existing Rails app, you can use the built in generator to install Hyperstack. Best to create a new branch of course before trying this out. @@ -702,4 +682,3 @@ Another approach is to add a simple component using the component generator: and then mount this component using ERB from within an existing view: `<% render_component 'HyperTest' %>` - diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb index 62314587d..d4b5b6e49 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/tags.rb @@ -93,6 +93,7 @@ def find_component(name) def lookup_const(name) return nil unless name =~ /^[A-Z]/ + return Hyperstack::Internal::Component::Tags::FRAGMENT if name == "FRAGMENT" scopes = self.class.name.to_s.split('::').inject([Object]) do |nesting, next_const| nesting + [nesting.last.const_get(next_const)] end.reverse diff --git a/ruby/hyper-component/spec/client_features/component_spec.rb b/ruby/hyper-component/spec/client_features/component_spec.rb index 251d806df..b004caace 100644 --- a/ruby/hyper-component/spec/client_features/component_spec.rb +++ b/ruby/hyper-component/spec/client_features/component_spec.rb @@ -227,7 +227,7 @@ class SomeLIs < Hyperloop::Component expect(page.find('div').text).to end_with("random string at the end") end - it "can generate multiple elements on outer render by returning arrays" do + it "can generate multiple elements on outer render by rendering multiple values" do mount 'Foo' do class Foo < Hyperloop::Component render do @@ -246,6 +246,49 @@ class SomeLIs < Hyperloop::Component expect(page.find('div').text).to end_with("random string at the end") end + it "fragments can be nested and have keys" do + mount 'Foo' do + class Foo < Hyperloop::Component + render do + UL(key: 1) do + 3.times do |i| + FRAGMENT(key: i) do + LI { "first #{i}" } + LI { "second #{i}" } + end + end + end + end + end + end + expect(page.find('ul').all('li').collect(&:text)).to eq([*0..2].collect { |i| ["first #{i}", "second #{i}"] }.flatten) + end + + xit "will only render once" do # see issue #329 + mount "Parent" do + class Child + include Hyperstack::Component + param_accessor_style :accessors + param :do_something + render do + puts "child: #{do_something.object_id}" + end + end + class Parent + include Hyperstack::Component + param_accessor_style :accessors + before_mount do + @do_something = -> { puts "we did it!" } + after(2) { force_update! } + end + render do + puts "parent: #{@do_something.object_id}" + Child(do_something: @do_something) + end + end + end + end + it 'can buffer an element' do mount 'Foo' do class Bar < Hyperloop::Component diff --git a/ruby/rails-hyperstack/.ruby-version b/ruby/rails-hyperstack/.ruby-version index ec1cf33c3..37c2961c2 100644 --- a/ruby/rails-hyperstack/.ruby-version +++ b/ruby/rails-hyperstack/.ruby-version @@ -1 +1 @@ -2.6.3 +2.7.2 diff --git a/ruby/rails-hyperstack/Rakefile b/ruby/rails-hyperstack/Rakefile index 38da6d1f5..845f0bc09 100644 --- a/ruby/rails-hyperstack/Rakefile +++ b/ruby/rails-hyperstack/Rakefile @@ -10,10 +10,10 @@ namespace :spec do opal_version = `bundle info opal`.match(/\* opal \((.+)\)/)[1] Dir.chdir('spec') do sh('rm -rf test_app') - Bundler.with_clean_env do + Bundler.with_unbundled_env do sh("rails _#{rails_version}_ new test_app -T") end - Bundler.with_clean_env do + Bundler.with_unbundled_env do Dir.chdir('test_app') do sh('cat ../gems.rb >> Gemfile') sh("echo 'gem \"opal\", \"#{opal_version}\"' >> Gemfile") diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb index 9b6b5b15a..5294278d1 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb @@ -9,13 +9,6 @@ class InstallGenerator < Rails::Generators::Base class_option 'webpack-only', type: :boolean class_option 'hyper-model-only', type: :boolean - # def add_clexer - # gem 'c_lexer' - # Bundler.with_clean_env do - # run 'bundle update' - # end - # end - def add_component if skip_adding_component? # normally this is handled by the hyper:component @@ -117,14 +110,23 @@ def cancel_react_source_import def install_webpacker return if skip_webpack? - gem 'webpacker' - Bundler.with_clean_env do - run 'bundle install' + + gem "webpacker" unless defined? ::Webpacker + Bundler.with_unbundled_env do + run "bundle install" end `spring stop` Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails webpacker:install' } end + def check_javascript_link_directory + manifest_js_file = Rails.root.join("app", "assets", "config", "manifest.js") + return unless File.exist? manifest_js_file + return unless File.readlines(manifest_js_file).grep(/javascript \.js/).empty? + + append_file manifest_js_file, "//= link_directory ../javascripts .js\n" + end + def create_policies_directory return if skip_hyper_model? policy_file = Rails.root.join('app', 'policies', 'hyperstack', 'application_policy.rb') diff --git a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb index 448b6e8e1..8a1d30a4c 100644 --- a/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/install/hyperstack_generator.rb @@ -214,7 +214,7 @@ def add_gems end def install - Bundler.with_clean_env do + Bundler.with_unbundled_env do run "bundle install" end run 'bundle exec rails webpacker:install' diff --git a/ruby/rails-hyperstack/rails-hyperstack.gemspec b/ruby/rails-hyperstack/rails-hyperstack.gemspec index b38872ea9..2e5e7d425 100644 --- a/ruby/rails-hyperstack/rails-hyperstack.gemspec +++ b/ruby/rails-hyperstack/rails-hyperstack.gemspec @@ -57,7 +57,7 @@ You can control how much of the stack gets installed as well: spec.add_dependency 'hyper-model', Hyperstack::VERSION spec.add_dependency 'hyper-router', Hyperstack::ROUTERVERSION spec.add_dependency 'hyperstack-config', Hyperstack::VERSION - spec.add_dependency 'opal-rails'#, '~> 1.0' + spec.add_dependency 'opal-rails' #, '~> 2.0' spec.add_dependency 'opal-browser', '~> 0.2.0' spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0' @@ -74,7 +74,7 @@ You can control how much of the stack gets installed as well: spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'rubocop', '~> 0.51.0' spec.add_development_dependency 'sqlite3', '~> 1.4' # was 1.3.6 -- see https://github.com/rails/rails/issues/35153 - spec.add_development_dependency 'sass-rails', '~> 5.0' + spec.add_development_dependency 'sass-rails', '>= 5.0' # Use Uglifier as compressor for JavaScript assets spec.add_development_dependency 'uglifier', '>= 1.3.0' # See https://github.com/rails/execjs#readme for more supported runtimes From 0a395e8ffe1c0b5ca7fe3ca07c58c05a2099c445 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 16 Mar 2021 00:22:00 -0400 Subject: [PATCH 202/307] more doc updates and intermittent hyper-operation spec fixes --- docs/client-dsl/README.md | 13 +++-- docs/client-dsl/components.md | 50 +++++++++++-------- docs/client-dsl/html-css.md | 25 ++++++++-- .../spec/aaa_run_first/transports_spec.rb | 3 +- 4 files changed, 60 insertions(+), 31 deletions(-) diff --git a/docs/client-dsl/README.md b/docs/client-dsl/README.md index f726f40bd..1464343d5 100644 --- a/docs/client-dsl/README.md +++ b/docs/client-dsl/README.md @@ -9,12 +9,17 @@ Your Hyperstack Application is built from a series of *Components* which are Rub # which supplies the DSL to translate from Ruby into React # function calls class Clock < HyperComponent - # before_mount is an example of a life cycle method - before_mount { every(1.second) { mutate @current_time = Time.now } } + # before_mount is an example of a life cycle method. + before_mount do + # before the component is first rendered (mounted) + # we setup a periodic timer that will update the + # current_time instance variable every second. + every(1.second) { mutate @current_time = Time.now } + end # every component has a render block which describes what will be # drawn on the UI render do - # conponents can render other components or primitive HTML or SVG + # Components can render other components or primitive HTML or SVG # tags. Components also use their state to determine what to render, # in this case the @current_time instance variable DIV { @current_time.strftime("%m/%d/%Y %I:%M:%S") @@ -26,7 +31,7 @@ This documentation will cover the following core concepts, many of which are touched on in the above simple example: + [HTML & CSS DSL](html-css.md) which provides Ruby implementations of all of the HTML and CSS elements -+ [Component DSL](components.md) is a Ruby DSL which wraps ReactJS Components ++ [The Component DSL](components.md) is a Ruby DSL which wraps ReactJS Components + [Lifecycle Methods](lifecycle-methods.md) are methods which are invoked before, during and after rendering + [State](state.md) - components rerender as needed when state changes. + [Event Handlers](event-handlers.md) allow any HTML element or Component to respond to an event, plus custom events can be described. diff --git a/docs/client-dsl/components.md b/docs/client-dsl/components.md index 2e4fd4fe3..21999b408 100644 --- a/docs/client-dsl/components.md +++ b/docs/client-dsl/components.md @@ -6,7 +6,7 @@ The DSL has the following major areas: * The `HyperComponent` class * HTML DSL elements -* Component Lifecycle Methods \(`before_mount`, `render`, `after_mount`, `after_update`, `after_error`\) +* Component Lifecycle Methods \(`before_mount`, `after_mount`, `after_update`\) * The `param` and `render` methods * Event handlers * Miscellaneous methods @@ -23,6 +23,7 @@ end # and is used like this: class AnotherComponent < HyperComponent + ... end ``` @@ -30,7 +31,7 @@ Having an Application wide HyperComponent class allows you to modify component b ## The `render` Callback -At a minimum every component class must define a `render` callback which returns one or more child elements. Those children may in turn have an arbitrarily deep structure. +At a minimum every component class must define a `render` block which returns one or more child elements. Those children may in turn have an arbitrarily deep structure. ```ruby class Component < HyperComponent @@ -40,7 +41,7 @@ class Component < HyperComponent end ``` -To save a little typing you can also include the top level element to be rendered: +To save a little typing you can also specify the top level element to be rendered: ```ruby class Component < HyperComponent @@ -50,7 +51,7 @@ class Component < HyperComponent end ``` -To render a component, you reference its class name in the DSL as a method call. This creates a new instance, passes any parameters proceeds with the component lifecycle. +To render a component, you reference its class name as a method call from another component. This creates a new instance, passes any parameters and proceeds with the component lifecycle. ```ruby class FirstComponent < HyperComponent @@ -60,17 +61,18 @@ class FirstComponent < HyperComponent end ``` -Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method. +Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method. + +> The one exception to using `new` is within a spec to create a "headless" component in order to access its internal state and methods. ### Invoking Components > Note: when invoking a component **you must have** a \(possibly empty\) parameter list or \(possibly empty\) block. - -```ruby +> ```ruby MyCustomComponent() # ok MyCustomComponent {} # ok MyCustomComponent # <--- breaks -``` +> ``` ## Multiple Components @@ -89,9 +91,9 @@ class Avatar < HyperComponent param :user_name render(DIV) do - # the user_name param has been converted to @UserName immutable instance variable - ProfilePic(user_name: @UserName) - ProfileLink(user_name: @UserName) + # for each param a method with the same name is defined + ProfilePic(user_name: user_name) + ProfileLink(user_name: user_name) end end @@ -99,15 +101,15 @@ class ProfilePic < HyperComponent param :user_name render do - IMG(src: "https://graph.facebook.com/#{@UserName}/picture") + IMG(src: "https://graph.facebook.com/#{user_name}/picture") end end class ProfileLink < HyperComponent param :user_name render do - A(href: "https://www.facebook.com/#{@UserName}") do - @UserName + A(href: "https://www.facebook.com/#{user_name}") do + user_name end end end @@ -207,16 +209,20 @@ class MyComponent < HyperComponent end ``` -### The children method +> The `to_key` method: Any ruby object can be a key. Under the hood the HyperComponent DSL will call the object's `to_key` method which will respond with a unique value representing the object. For example if you pass an ActiveRecord model instance as a key, the result will be the database id of the model. + +### The children and render methods -Along with params components may be passed a block which is used to build the components children. +Along with the params hash components may be passed a block which is used to build the components children. -The instance method `children` returns an enumerable that is used to access the unrendered children of a component. +The instance method `children` returns an enumerable that is used to access the *unrendered* children of a component. The children can then be rendered +using the `render` method which will merge any additional parameters and +render the child. ```ruby class Indenter < HyperComponent render(DIV) do - IndentEachLine(by: 100) do # see IndentEachLine below + IndentEachLine(by: 10) do # see IndentEachLine below DIV {"Line 1"} DIV {"Line 2"} DIV {"Line 3"} @@ -267,7 +273,7 @@ class ListItems < HyperComponent end ``` -The only param that FRAGMENT may take is a key, which is useful if there will be multiple fragments being merged, at some higher level. +The only param that FRAGMENT may take is a key, which is useful if there will be multiple fragments being merged at some higher level. ### Data Flow @@ -276,7 +282,7 @@ In React, data flows from owner to owned component through the params as discuss ### Stores -Managing state between components is best done using Stores as many Components can access one store. This saves passing data btween Components. Please see the [Store documentation](https://github.com/hyperstack-org/hyperstack/tree/a530e3955296c5bd837c648fd452617e0a67a6ed/docs/dsl-client/hyper-store/README.md) for details. +Managing state between components is best done using Stores as many Components can access one store. This saves passing data between Components. Please see the [Store documentation](https://docs.hyperstack.org/client-dsl/hyper-store) for details. ### Reusable Components @@ -294,7 +300,7 @@ Examples: ```ruby param :foo # declares that we must be provided with a parameter foo when the component is instantiated or re-rerendered. -param :foo, alias: :something # the alias name will be used for the param (instead of @Foo) +param :foo, alias: :something # the alias name will be used for the param (instead of Foo) param :foo => "some default" # declares that foo is optional, and if not present the value "some default" will be used. param foo: "some default" # same as above using ruby 1.9 JSON style syntax param :foo, default: "some default" # same as above but uses explicit default key @@ -324,6 +330,8 @@ end A core design concept taken from React is that data flows down to child Components via params and params \(called props in React\) are immutable. +However for complex objects + In Hyperstack, there are **two exceptions** to this rule: * An instance of a **Store** \(passed as a param\) is mutable and changes to the state of the Store will cause a re-render diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index 1f1e8b91d..eae83d52f 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -14,19 +14,19 @@ end > **Notice that the HTML elements \(BUTTON, DIV, etc.\) are in CAPS**. We know this is bending the standard Ruby style rules, but we think it reads better this way. -For example, to render a `
`: +For example ```ruby DIV(class: 'green-text') { "Let's gets started!" } ``` -Would create the following HTML: +would create the following HTML: ```markup
Let's gets started!
``` -And to render a table: +And this would render a table: ```ruby TABLE(class: 'ui celled table') do @@ -52,7 +52,7 @@ The following HTML and SVG elements are available:
HTML Tags -```markup +``` A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON CANVAS CAPTION CITE CODE COL COLGROUP @@ -80,7 +80,7 @@ WBR
SVG Tags -```markup +``` CIRCLE CLIPPATH DEFS ELLIPSE @@ -123,3 +123,18 @@ For `style` you need to pass a hash using the [React style conventions](https:// ```ruby PARA(style: { display: item[:some_property] == "some state" ? :block : :none }) ``` + +### Complex Arguments + +You can pass multiple hashes which will be merged, and any individual symbols +(or strings) will be treated as `=true`. For example + +```ruby +A(:flag, {href: '/'}, {class: 'my_class'}) +``` + +will generate + +```HTML + +``` diff --git a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb index a10608b1b..34afce014 100644 --- a/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb +++ b/ruby/hyper-operation/spec/aaa_run_first/transports_spec.rb @@ -200,11 +200,12 @@ def connect(*args) it 'sees the connection going offline' do mount 'TestComponent' + puts "active connections after mounting: #{Hyperstack::Connection.active}" evaluate_ruby 'Hyperstack.go_ahead_and_connect' Timecop.travel(Time.now + Hyperstack::Connection.transport.expire_new_connection_in - 1) wait_for do sleep 0.25 - Hyperstack::Connection.active + Hyperstack::Connection.active.tap { |c| puts "active connections now = #{c}"} end.to eq(['ScopeIt::TestApplication']) ApplicationController.acting_user = true mount 'TestComponent' From 836946c9ace52000d6b3d1ad2baab3ba74f1fe3f Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 16 Mar 2021 11:44:50 -0400 Subject: [PATCH 203/307] closes #361 --- docs/client-dsl/components.md | 10 ++++- ruby/rails-hyperstack/Rakefile | 1 + .../hyperstack/install_generator.rb | 25 ++++++++---- .../spec/rails_hyperstack_spec.rb | 38 +++++++++++-------- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/docs/client-dsl/components.md b/docs/client-dsl/components.md index 21999b408..d69bd35c3 100644 --- a/docs/client-dsl/components.md +++ b/docs/client-dsl/components.md @@ -13,11 +13,17 @@ The DSL has the following major areas: ## HyperComponent -By convention your Hyperstack Components will inherit from the `HyperComponent` class, which at a minimum looks like this: +By convention your Hyperstack Components will inherit from the `HyperComponent` class, which typically would look like this: ```ruby class HyperComponent + # All component classes must include Hyperstack::Component include Hyperstack::Component + # The Observable module adds state handling + include Hyperstack::State::Observable + # The following turns on the new style param accessor + # i.e. param :foo is accessed by the foo method + param_accessor_style :accessors end # and is used like this: @@ -330,7 +336,7 @@ end A core design concept taken from React is that data flows down to child Components via params and params \(called props in React\) are immutable. -However for complex objects +However for complex objects In Hyperstack, there are **two exceptions** to this rule: diff --git a/ruby/rails-hyperstack/Rakefile b/ruby/rails-hyperstack/Rakefile index 845f0bc09..70271475f 100644 --- a/ruby/rails-hyperstack/Rakefile +++ b/ruby/rails-hyperstack/Rakefile @@ -22,6 +22,7 @@ namespace :spec do sh('bundle exec rails g hyperstack:install') sh('bundle exec rails generate model Sample name:string description:text') sh('mv app/models/sample.rb app/hyperstack/models/sample.rb') + sh("cat ../server_side_sample.rb >> app/models/sample.rb") sh('bundle exec rake db:migrate') sh('RAILS_ENV=test bundle exec rake db:setup') # sh('bundle exec rails dev:cache') # not tested yet... diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb index 5294278d1..2bd69bcd8 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb @@ -122,7 +122,7 @@ def install_webpacker def check_javascript_link_directory manifest_js_file = Rails.root.join("app", "assets", "config", "manifest.js") return unless File.exist? manifest_js_file - return unless File.readlines(manifest_js_file).grep(/javascript \.js/).empty? + return unless File.readlines(manifest_js_file).grep(/javascripts \.js/).empty? append_file manifest_js_file, "//= link_directory ../javascripts .js\n" end @@ -162,13 +162,13 @@ def move_and_update_application_record unless File.exist? hyper_app_record_file empty_directory Rails.root.join('app', 'hyperstack', 'models') `mv #{rails_app_record_file} #{hyper_app_record_file}` - create_file rails_app_record_file, <<-RUBY -# #{rails_app_record_file} -# the presence of this file prevents rails migrations from recreating application_record.rb -# see https://github.com/rails/rails/issues/29407 - -require 'models/application_record.rb' - RUBY +# create_file rails_app_record_file, <<-RUBY +# # #{rails_app_record_file} +# # the presence of this file prevents rails migrations from recreating application_record.rb +# # see https://github.com/rails/rails/issues/29407 +# +# require 'models/application_record.rb' +# RUBY end end @@ -245,6 +245,15 @@ def inject_into_initializer(s) else create_file file_name, <<-RUBY #{s} +# server_side_auto_require will patch the ActiveSupport Dependencies module +# so that you can define classes and modules with files in both the +# app/hyperstack/xxx and app/xxx directories. For example you can split +# a Todo model into server and client related definitions and place this +# in `app/hyperstack/models/todo.rb`, and place any server only definitions in +# `app/models/todo.rb`. + +require "hyperstack/server_side_auto_require.rb" + # set the component base class Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' diff --git a/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb b/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb index 619c4a8bd..70b9680be 100644 --- a/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb +++ b/ruby/rails-hyperstack/spec/rails_hyperstack_spec.rb @@ -1,24 +1,30 @@ -require 'spec_helper' +require "spec_helper" -describe 'rails-hyperstack' do - it 'builds a working app', js: true do - visit '/' - expect(page).to have_content('App') +describe "rails-hyperstack" do + it "builds a working app", js: true do + visit "/" + expect(page).to have_content("App") end - it 'installs hyper-model and friends', js: true do - visit '/' - expect_promise do + + it "installs hyper-model and friends", js: true do + visit "/" + expect do Hyperstack::Model.load { Sample.count } - end.to eq(0) - evaluate_ruby do - Sample.create(name: 'sample1', description: 'the first sample') + end.on_client_to eq(0) + on_client do + Sample.create(name: "sample1", description: "the first sample") end wait_for_ajax expect(Sample.count).to eq(1) - expect(Sample.first.name).to eq('sample1') - expect(Sample.first.description).to eq('the first sample') - expect_evaluate_ruby do - Sample.count - end.to eq(1) + expect(Sample.first.name).to eq("sample1") + expect(Sample.first.description).to eq("the first sample") + expect { Sample.count }.on_client_to eq(1) + end + + it "implements server_side_auto_require", js: true do + expect(Sample.super_secret_server_side_method).to be true + expect do + Sample.respond_to? :super_secret_server_side_method + end.on_client_to be_falsy end end From 748d88c87f7e6c8ec854f05bf9c140812a0486de Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 16 Mar 2021 12:03:41 -0400 Subject: [PATCH 204/307] forgot to add new server_side_auto_require.rb file --- .../hyperstack/server_side_auto_require.rb | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb diff --git a/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb b/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb new file mode 100644 index 000000000..a3388d127 --- /dev/null +++ b/ruby/rails-hyperstack/lib/hyperstack/server_side_auto_require.rb @@ -0,0 +1,39 @@ +Rails.configuration.autoloader = :classic + +module ActiveSupport + module Dependencies + HYPERSTACK_DIR = "hyperstack" + class << self + alias original_require_or_load require_or_load + + # before requiring_or_loading a file, first check if + # we have the same file in the server side directory + # and add that as a dependency + + def require_or_load(file_name, const_path = nil) + add_server_side_dependency(file_name) + original_require_or_load(file_name, const_path) + end + + # search the filename path from the end towards the beginning + # for the HYPERSTACK_DIR directory. If found, remove it from + # the filename, and if a ruby file exists at that location then + # add it as a dependency + + def add_server_side_dependency(file_name) + path = File.expand_path(file_name.chomp(".rb")) + .split(File::SEPARATOR).reverse + hs_index = path.find_index(HYPERSTACK_DIR) + + return unless hs_index # no hyperstack directory here + + new_path = (path[0..hs_index - 1] + path[hs_index + 1..-1]).reverse + load_path = new_path.join(File::SEPARATOR) + + return unless File.exist? "#{load_path}.rb" + + require_dependency load_path + end + end + end +end From bb87febf6f99cdaaacf0056995fd66db4edb7b54 Mon Sep 17 00:00:00 2001 From: catmando Date: Tue, 16 Mar 2021 12:14:29 -0400 Subject: [PATCH 205/307] and I forgot this file too --- ruby/rails-hyperstack/spec/server_side_sample.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ruby/rails-hyperstack/spec/server_side_sample.rb diff --git a/ruby/rails-hyperstack/spec/server_side_sample.rb b/ruby/rails-hyperstack/spec/server_side_sample.rb new file mode 100644 index 000000000..556d67f79 --- /dev/null +++ b/ruby/rails-hyperstack/spec/server_side_sample.rb @@ -0,0 +1,5 @@ +class Sample < ApplicationRecord + def self.super_secret_server_side_method + true + end +end From 4fa7b2a097f2392861646ca1f4e65e634d13fc81 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 17 Mar 2021 17:19:53 -0400 Subject: [PATCH 206/307] updated docs fixed some generator issues plus a regression from #375 --- docs/SUMMARY.md | 11 +- docs/installation/README.md | 51 ++++ docs/installation/man-installation.md | 59 +---- docs/rails-installation/README.md | 19 ++ docs/rails-installation/file-structure.md | 241 ++++++++++++++++++ docs/rails-installation/generators.md | 114 +++++++++ docs/rails-installation/other_details.md | 115 +++++++++ docs/rails-installation/prerequisites.md | 35 +++ .../rails-installation/using-the-installer.md | 38 +++ docs/tutorial/intro_to_hyper_components.md | 132 ++++++++++ release-notes/1.0.alpha1.5.md | 42 +++ release-notes/1.0.alpha1.6.md | 52 ++++ .../component/rails/component_mount.rb | 3 + .../lib/reactive_record/broadcast.rb | 5 +- .../hyper/templates/component_template.rb | 2 +- .../hyperstack/install_generator.rb | 186 +++----------- .../hyperstack/install_generator_base.rb | 170 +++++++++++- 17 files changed, 1066 insertions(+), 209 deletions(-) create mode 100644 docs/rails-installation/README.md create mode 100644 docs/rails-installation/file-structure.md create mode 100644 docs/rails-installation/generators.md create mode 100644 docs/rails-installation/other_details.md create mode 100644 docs/rails-installation/prerequisites.md create mode 100644 docs/rails-installation/using-the-installer.md create mode 100644 docs/tutorial/intro_to_hyper_components.md create mode 100644 release-notes/1.0.alpha1.5.md create mode 100644 release-notes/1.0.alpha1.6.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5b96f359b..4f3c9f354 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,10 +1,12 @@ # Table of contents * [Welcome](README.md) -* [Installation](installation/README.md) - * [Installation](installation/man-installation.md) - * [Configuration](installation/config.md) - * [Upgrading from legacy Hyperloop](installation/upgrading.md) +* [Rails Installation and Configuration](rails-installation/README.md) + * [Prerequisites](rails-installation/prerequistes.md) + * [Using the Hyperstack Installer](rails-installation/using-the-installer.md) + * [Using the Generators](rails-installation/generators.md) + * [File Structure](rails-installation/file-structure.md) + * [Other Rails Configuration Details](rails-installation/other-details.md) * [Client DSL](client-dsl/README.md) * [HTML & CSS DSL](client-dsl/html-css.md) * [Component DSL](client-dsl/components.md) @@ -29,6 +31,7 @@ * [Methods and Features](development-workflow/hyper-spec/03-methods-and-features.md) * [Using with Rack](development-workflow/hyper-spec/04-using-with-rack.md) * [Tools](development-workflow/tools.md) + * [Deploy To Heroku](development-workflow/deploy-to-heroku.md) * [Tutorial](tutorial/README.md) * [TodoMVC Tutorial Part I](tutorial/todo.md) * [TodoMVC Tutorial Part II](tutorial/todo-part-2.md) diff --git a/docs/installation/README.md b/docs/installation/README.md index 4026317a8..7ba05740e 100644 --- a/docs/installation/README.md +++ b/docs/installation/README.md @@ -1,2 +1,53 @@ # Installation +The easiest way to install Hyperstack in either a new or existing Rails app is to run installer. + +## Pre-Requisites + +#### - Rails >= 5.x + +[Rails Install Instructions](http://railsinstaller.org/en) + +#### - Yarn + +For a full system install including webpacker for managing javascript assets you will +need yarn. To skip adding webpacker use `hyperstack:install:skip-webpack` + +[Yarn Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) + +## - Creating a New Rails App + +If you don't have an existing Rails app to add Hyperstack to, you can create a new Rails app +with the following command line: + +``` +bundle exec rails new NameOfYourApp -T +``` + +To avoid much pain, do not name your app `Application` as this will conflict with all sorts of +things in Rails and Hyperstack. + +Once you have created the app cd into the newly created directory. + +> The -T option will skip adding minitest directories, as Hyperstack prefers RSpec. + +## - Installing HyperStack + +* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your gem file +* run `bundle install` +* run `bundle exec rails hyperstack:install` + +> Note: if you want to use the unreleased edge branch your gem specification will be: +> +> ```ruby +> gem 'rails-hyperstack', +> git: 'git://github.com/hyperstack-org/hyperstack.git', +> branch: 'edge', +> glob: 'ruby/*/*.gemspec' +> ``` +> + +## - Start the Rails app + +* `bundle exec foreman start` to start Rails and the Hotloader +* Navigate to `http://localhost:5000/` diff --git a/docs/installation/man-installation.md b/docs/installation/man-installation.md index 4b1056520..07ce23e82 100644 --- a/docs/installation/man-installation.md +++ b/docs/installation/man-installation.md @@ -1,45 +1,4 @@ -# Installation - -You can install Hyperstack either - -* using a Rails generator, -* or manually walking through the detailed installation instructions below. - -Even if you use the generator later reading through the detailed installation instructions can be helpful to understand how the system fits together and the generators work. - -## Pre-Requisites - -#### - Rails >= 5.x [Install Instructions](http://railsinstaller.org/en) - -And for a full system as setup generator you will need - -#### - Yarn [Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) - -## Installing Using the Generator - -If you have an existing Rails app, you can use the built in generator to install Hyperstack. Best to create a new branch of course before trying this out. - -* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your gem file -* run `bundle install` -* run `bundle exec rails g hyperstack:install` - -> Note: if you want to use the unreleased edge branch your gem specification will be: -> -> ```ruby -> gem 'rails-hyperstack', -> git: 'git://github.com/hyperstack-org/hyperstack.git', -> branch: 'edge', -> glob: 'ruby/*/*.gemspec' -> ``` -> -> ### Start the Rails app - -* `bundle exec foreman start` to start Rails and the Hotloader -* Navigate to `http://localhost:5000/` - -> Note that the generator will add a wild card route to the beginning of your routes file. This will let you immediately test Hyperstack, but will also mean that all of your existing routes are now unreachable. So after getting Hyperstack up, you will want to adjust things to your needs. See the first in the **Manual Installation** section for more info. - -## Manual Installation +# Manual Installation To manually install a complete Hyperstack system there are quite a few steps. However these can be broken down into six separate activities each of which will leave your system in a working and testable state: @@ -87,6 +46,7 @@ and visit `localhost:3000/test` and you will see **TestApp** displayed. This command accomplishes four tasks which you can also manually perform if you prefer: 1. Insure that the `hyperstack-loader` is required in your `application.js` file; +2. Insure that the webpacker manifest file is loading JS files; 2. Insure that you have a `HyperComponent` base class defined; 3. Add a skeleton `TestApp` component and 4. Add a route to the `TestApp` component in your rails routes file. @@ -104,7 +64,15 @@ The `hyperstack-loader` is a dynamically generated asset manifest that will load //= require_tree . ``` -Once this is added to your `application.js` file you will see the hyperstack asset manifest on the Rails console and the browser's debug console which looks like this: +#### Check the webpacker manifest file + +In Rails 6 the webpacker default installation no longer links to the javascripts directory, but because Opal uses sprockets we need to add it back in. Check to see if you have a `app/assets/config/manifest.js` file, and if you do, insure it has the following line: + +```javascript +//= link_directory ../javascripts .js +``` + +Once hyperstack-loader and link_directory directives are added you can reload any page you will see the hyperstack asset manifest on the Rails console and the browser's debug console which looks like this: ```text require 'opal' @@ -133,7 +101,7 @@ require 'config/initializers/inflections.rb' All your Hyperstack code goes into the `app/hyperstack` directory, which is at the same level as `app/models`, `app/views`, `app/controllers`, etc. -Inside this directory are subdirectories for each of the different parts of you hyperstack application code. Components go in the `app/hyperstack/components` directory. +Inside this directory are subdirectories for each of the different parts of you Hyperstack application code. Components go in the `app/hyperstack/components` directory. Like Rails models, and Rails controllers, Hyperstack components by convention inherit from an application defined base class. So while a typical Rails model inherits from the `ApplicationRecord` class, your Hyperstack components will inherit from the `HyperComponent` class. @@ -222,7 +190,6 @@ will render the component named `TestApp` at that position in your view. You can also use `render_component` in a controller in place of the standard Rails render method. Like the `render_component` view helper you can pass the component parameters in from the controller. -There are quite a few steps, but each has a specific, and understandable purpose. ## Installing the Hotloader @@ -232,7 +199,7 @@ There are three steps to installing the Hotloader: 1. Importing Hotloader into your `hyperstack-loader` manifest; 2. Adding the `foreman` gem to your `Gemfile` and -3. Adding a `Procfile` to the root of our application directory. +3. Adding a `Procfile` to the root of our application directory.`` By default the Hotloader is **not** included in the hyperstack manifest so the first step is to add the `config/initializers/hyperstack.rb` initializer file, and _import_ the Hotloader: diff --git a/docs/rails-installation/README.md b/docs/rails-installation/README.md new file mode 100644 index 000000000..e88322214 --- /dev/null +++ b/docs/rails-installation/README.md @@ -0,0 +1,19 @@ +## Rails Installation + +The easiest way to get the full benefit of Hyperstack is to integrate it with a Rails application. + +Adding Hyperstack to your existing Rails App is as simple as adding the gem and running the installer. + +Read on to make sure you have the necessary prerequisites. +> #### Why Rails? +> +>Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality. +> +>People sometimes balk at Rails because when they see the huge number of files and directories generated by the Rails installer, it looks crazy, complex, and ineffecient. Keep in mind that this has very little if any impact on your applications performance, and when developing code 90% of your time will be spent in the following directories: `app/models` and `app/hyperstack`. The rest of the files are there to hold configuration files, and seldom used content, so they have a place out of the way of your main development activities. +> +>Developers often believe that Rails modules like ActionController and ActiveRecord while powerful are slow. +In the case of Hyperstack this is largely irrelevant since one of our goals is to offload as much work to the client as possible. For example rather than have a multitude of controllers delivering different page views and updates, your client side Hyperstack code is now responsible for that. The role of the server becomes the central database, and the place where secure operations are executed (such as sending mail, authenticating users etc.) +> +>### How about other Rack Frameworks +> +>But still you may have specific needs for a lighter weight system, or have an existing Sinatra app (for example) that you would like to use with Hyperstack. For now we will say its in the plan, and its just a matter of time. If you are interested leave a comment on this issue: https://github.com/hyperstack-org/hyperstack/issues/340 diff --git a/docs/rails-installation/file-structure.md b/docs/rails-installation/file-structure.md new file mode 100644 index 000000000..e822b729f --- /dev/null +++ b/docs/rails-installation/file-structure.md @@ -0,0 +1,241 @@ +## Application File Structure + +Hyperstack adds the following files and directories to your Rails +application: + +``` +/app/hyperstack +/app/operations +/app/policies +/config/initializers/hyperstack.rb +/app/javascript/packs/client_and_server.js +/app/javascript/packs/client_only.js +``` + +In addition there are configuration settings in existing Rails files that are explained in the next section. Below we cover the purpose of each these files, and their contents. + +### The `/app/hyperstack/` Directory + +Here lives all your Hyperstack code. Some of the subdirectories are *isomorphic* meaning the code is shared between the client and the server, other directories are client only. + +Within the `hyperstack` directory there will be the following sub-directories: + ++ `components` *(client-only)* is where your components live. + Following Rails conventions a component with a class of Bar::None::FooManchu should be in a file named `components/bar/none/foo_manchu.rb` + ++ `models` *(isomorphic)* is where ActiveRecord models are shared with the client. More on this below. + ++ `operations` *(isomorphic)* is where Hyperstack Operations will live. + ++ `shared` *(isomorphic)* is where you can put shared code that are not models or operations. + ++ Any other subdirectory (such as `libs` and `client-ops`) will be considered client-only. + +### Sharing Models and Operations + +Files in the `hyperstack` `/models` and `/operations` directories are loaded on the client and the server. So when you place a model's class definition in the `hyperstack/models` directory the class is available on the client. + +```Ruby + Todo.count # will return the same value on the client and the server +``` + +> See the Policy section below for how access to the actual data is controlled. Remember a Model *describes* some data, but the actual data is stored in the database, and protected by Policies. + +Likewise Operations placed in the `/operations` directory can be run on the client or the server, or in the case of a `ServerOp` the operation can be invoked on the client, but will run on the server. + +Hyperstack sets things up so that Rails will first look in the `hyperstack` `/models` and `/operations` directories, and then in the server only `app/models` and `app/operations` directories. So if you don't want some model shared you can just leave it in the normal `app` directory. + +### Splitting Class Definitions + +There are cases where you would like split a class definition into its shared and server-only aspects. For example there may be code in a model that cannot be sensibly run on the client. Hyperstack augments the Rails dependency lookup mechanism so that when a file is found in a `hyperstack` directory we will *also* load any matching file in the normal `app` directory. + +This works because Ruby classes are *open*, so that you can define a class (or module) in multiple places. + +### Server Side Operations + +Operations are Hyperstack's way of providing *Service Objects*: classes that perform some operation not strictly belonging to a single model, and often involving other services such as remote APIs. + +As such Operations can be useful strictly on the server side, and so can be added to the `app/operations` directory. + +Server side operations can also be remotely run from the client. Such operations are defined as subclasses of `Hyperstack::ServerOp`. + +The right way to define a `ServerOp` is to place its basic definition including its parameter signature in the `hyperstack/operations` directory, and then placing the rest of the operations definition in the `app/operations` directory. + +### Policies + +Hyperstack uses Policies to define access rights to your models. Policies are placed in the `app/policies` directory. For example the policies for the `Todo` model would defined by the `TodoPolicy` class located at `app/policies/todo_policy.rb` Details on policies can be found [Policy section of this document.](https://docs.hyperstack.org/isomorphic-dsl/hyper-policy). + +### Example Directory Structure +``` +└── app/ + ├── models/ + │ └── user.rb # private section of User model + ├── operations/ + │ └── email_the_owner.rb # server code + ├── hyperstack/ + │ ├── components/ + │ │ ├── app.rb + │ │ ├── edit_todo.rb + │ │ ├── footer.rb + │ │ ├── header.rb + │ │ ├── show_todo.rb + │ │ └── todo_index.rb + │ ├── models/ + │ │ ├── application_record.rb # usually no need to split this + │ │ ├── todo.rb # note all of Todo definition is public + │ │ └── user.rb # user has a public and private section + │ └── operations/ + │ └── email_the_owner.rb # serverop interface only + └── policies/ + ├── todo_policy.rb + └── user_policy.rb + +``` + +These directories are where most of your work will be done during Hyperstack development. + +> #### What about Controllers and Views? +Hyperstack works alongside Rails controllers and views. In a clean-sheet Hyperstack app you never need to create a controller or a view. On the other hand if you have existing code or aspects of your project that you feel would work better using a traditional MVC approach everything will work fine. You can also merge the two worlds: Hyperstack includes two helpers that allow you to mount components either from a controller or from within a view. + +### The Hyperstack Initializer + +The Hyperstack configuration can be controlled via the `config/initializers/hyperstack.rb` initializer file. Using the installer will set up a reasonable set of of options, which you can tweak as needed. + +Here is a summary of the various configuration settings: + +```ruby +# config/initializers/hyperstack.rb + +# server_side_auto_require will patch the ActiveSupport Dependencies module +# so that you can define classes and modules with files in both the +# app/hyperstack/xxx and app/xxx directories. + +require "hyperstack/server_side_auto_require.rb" + +# By default the generators will generate new components as subclasses of +# HyperComponent. You can change this using the component_base_class setting. + +Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' + +# prerendering is default :off, you should wait until your +# application is relatively well debugged before turning on. + +Hyperstack.prerendering = :off # or :on + +# The transport setting controls how push (websocket) communications are +# implemented. The default is :none, but will be set to :action_cable if you +# install hyper-model. + +# Other possibilities are :action_cable, :pusher (see www.pusher.com) +# or :simple_poller which is sometimes handy during system debug. + +Hyperstack.transport = :action_cable # :pusher, :simple_poller or :none + +# hotloader settings: +# sets the port hotloader will listen on. Note this must match the value used +# to start the hotloader typically in the foreman Procfile. +Hyperstack.hotloader_port = 25222 +# seconds between pings over the hotloader websocket. Normally not needed. +Hyperstack.hotloader_ping = nil +# hotloader will automatically reload callbacks when effected classes are +# reloaded. Not recommended to change this. +Hyperstack.hotloader_ignore_callback_mapping = false + +# Transport settings +# seconds before timeout when sending messages between the rails console and +# the server. +Hyperstack.send_to_server_timeout = 10 + +# Transport specific options +Hyperstack.opts, { + # pusher specific options + app_id: 'your pusher app id', + key: 'your pusher key', + secret: 'your pusher secret', + cluster: 'mt1', # pusher cluster defaults to mt1 + encrypted: true, # encrypt pusher comms, defaults to true + refresh_channels_every: 2.minutes, # how often to check which channels are alive + + # simple poller specific options + expire_polled_connection_in: 5.minutes, # when to kill simple poller connections + seconds_between_poll: 5.seconds, # how fast to poll when using simple poller + expire_new_connection_in: 10.seconds, # how long to keep initial sessions alive +} + +# Namespace used to keep hyperstack communication separate from other websockets +Hyperstack.channel_prefix = 'synchromesh' + +# If there a JS console available should websocket comms be logged? +Hyperstack.client_logging = true + +# Automatically create a (possibly temporary) websocket connection as each +# browser session starts. Usually this is needed for further authentication and +# should be left as true +Hyperstack.connect_session = true + +# Where to store the connection tables. Default is :active_record but you +# can also specify redis. If specifying redis the redis url defaults to +# redis://127.0.0.1:6379 +Hyperstack.connection = [adapter: :active_record] # or + # [adapter: :redis, redis_url: 'redis://127.0.0.1:6379] + +# The import directive loads optional portions of the various hyperstack gems. +# Here are the common imports typically included: + +Hyperstack.import 'hyperstack/hotloader', client_only: true if Rails.env.development? + +# and these are typically not imported: + +# React source is normally brought in through webpacker +# Hyperstack.import 'react/react-source-browser' + +# add this line if you need jQuery AND ARE NOT USING WEBPACK +# Hyperstack.import 'hyperstack/component/jquery', client_only: true + +# The following are less common settings which you should never have to change: +Hyperstack.prerendering_files = ['hyperstack-prerender-loader.js'] +Hyperstack.public_model_directories = ['app/hyperstack/models'] + + +# change definition of on_error to control how errors such as validation +# exceptions are reported on the server +module Hyperstack + def self.on_error(operation, err, params, formatted_error_message) + ::Rails.logger.debug( + "#{formatted_error_message}\n\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' + ) + ) + end +end if Rails.env.development? +``` + +### Hyperstack Packs + +Rails `webpacker` organizes javascript into *packs*. Hyperstack will look for and load one of two packs depending on if you are prerendering or not. + +The default content of these packs are as follows: + +```javascript +//app/javascript/packs/client_and_server.js +// these packages will be loaded both during prerendering and on the client +React = require('react'); // react-js library +createReactClass = require('create-react-class'); // backwards compatibility with ECMA5 +History = require('history'); // react-router history library +ReactRouter = require('react-router'); // react-router js library +ReactRouterDOM = require('react-router-dom'); // react-router DOM interface +ReactRailsUJS = require('react_ujs'); // interface to react-rails +// to add additional NPM packages run `yarn add package-name@version` +// then add the require here. +``` + +```javascript +//app/javascript/packs/client_only.js +// add any requires for packages that will run client side only +ReactDOM = require('react-dom'); // react-js client side code +jQuery = require('jquery'); // remove if you don't need jQuery +// to add additional NPM packages call run yarn add package-name@version +// then add the require here. +``` diff --git a/docs/rails-installation/generators.md b/docs/rails-installation/generators.md new file mode 100644 index 000000000..dbb8e6872 --- /dev/null +++ b/docs/rails-installation/generators.md @@ -0,0 +1,114 @@ +## Hyperstack Generators + +As well as the installer Hyperstack includes two generators to create +basic component skeletons. + +### Summary: + +``` +bundle exec rails g hyper:component ComponentName # add a new component +bundle exec rails g hyper:router RouterName # add a new router component +``` + +both support the following flags: + ++ `--no-help` don't add extra comments and method examples ++ `--add-route=...` add a route to this component to the Rails routes file ++ `--base-class=...` change the base class name from the default + +### The Component Generator + +To add a new component skeleton use the `hyper:component` generator: + +``` +bundle exec rails g hyper:component ComponentName +``` + +#### File directories and Name Spacing Components + +The above will create a new class definition for `ComponentName` in a file named `component_name.rb` and place it in +the `app/hyperstack/components/` directory. The component may be name spaced and +will be placed in the appropriate subdirectory. I.e. `Foo::BarSki` will generate +`app/hyperstack/components/foo/barski.rb` + +#### The `--no-help` flag + +By default the skeleton will be verbose and contain examples of the most often used +class methods which you can keep or delete as needed. You can generate a minimal +component with the `--no-help` flag. + +### Router Generator + +Typically your top level component will be a *Router* which will take care of dispatching to specific components as the URL changes. This provides the essence of a *Single Page App* where as the user moves between parts of +the application the URL is updated, the back and forward buttons work, but the page is **not** reloaded from the server. + +A component becomes a router by including the `Hyperstack::Router` module +which provides a number of methods that will be used in the router +component. + +To generate a new router skeleton use the `hyper:router` generator: + +``` +bundle exec rails g hyper:router App +``` + +Note that we now have two routers to deal with. The server still has the `routes.rb` file that maps incoming requests to a Rails controller which +will provide the appropriate response. + +On the client the router there maps the current url to a component. + +#### Routing to Your Components from Rails + +Components can be directly mounted from the Rails `routes.rb` file, using the builtin Hyperstack controller. + +For example a Rails `routes.rb` file containing + +```ruby + get 'some_page/(*others)', to: 'hyperstack#some_component' +``` + +will route all urls beginning with `some_page` to `SomeComponent`. + +When you generate a new component you can use the `--add-route` option to add the route for you. For example: + +``` +bundle exec rails g hyper:router SomeComponent \ + --add-route="some_page/(*others)" +``` + +would add the route shown above. + +Note that typically the Rails route will be going to a Hyperstack Router Component. That is why we add the wild card to the Rails route so that all urls beginning with `some_page/` will all be handled by `SomeComponent`. + +Also note that for the purposes of the example we are using rather dubious names, a more logical setup would be: + +```ruby + get `/(*others)`, to 'hyperstack#app' +``` + +Which you could generate with +``` +bundle exec rails g hyper:router App --add-route="/(*others)" +``` + +#### Changing the Base Class + +By default components will inherit from the `HyperComponent` base class. + +You can globally override this by changing the value `Hyperstack.component_base_class` in the `hyperstack.rb` initializer. + +For example some teams prefer `ApplicationComponent` as their base class name. + +You can also override the base class name when generating a component using the `--base-class` option. + +This is useful when you have a common library subclass that other classes will inherit from. For example: + +``` +bundle exec rails g hyper:component UserBio --base-class=TextArea +``` +will generate +``` +class UserBio < TextArea +... +end +``` diff --git a/docs/rails-installation/other_details.md b/docs/rails-installation/other_details.md new file mode 100644 index 000000000..a647d55a6 --- /dev/null +++ b/docs/rails-installation/other_details.md @@ -0,0 +1,115 @@ +## Other Rails Configuration Details + +Hyperstack sets a number of Rails configurations as outlined below. + +>These are all setup +automatically by the hyperstack generators and installers. They are documented here for advanced configuration or in the sad chance that something gets broken during your setup. Please report any issues with setup, or if you feel you have to manually tweak things. + +#### Require the `hyperstack-loader` + +The `app/assets/javascripts/application.js` file needs to require the hyperstack-loader. + +```javascript +//= require hyperstack-loader // add as the last require directive +``` + +The loader handles bringing in client side code, getting it compiled (using sprockets) and adding it to the webpacks (if using webpacker.) + +> Note that now that Rails is using webpacker by default you may have to create +this file, and the single line above. If so be sure to checkout your layout +file, as the javascript_include_tag will also be missing there. + +#### `app/assets/config/manifest.js` + +If you are using webpacker this file must exist and contain the following line: + +```Ruby +//= link_directory ../javascripts .js +``` + +This line insures that the any javascript in the assets directory are included in the webpacks. In older versions of Rails, this line will already be there, and if not +using webpacker its actually not necessary (but doesn't hurt anything.) + +#### The application layout + +If using a recent version of rails with webpacker you may find that the application.html.erb file longer loads the application.js file. Make sure that your layout file has this line: + +```html + <%= javascript_include_tag 'application' %> +``` + +#### Required NPM modules + +If using Webpacker Hyperstack needs the following NPM modules: + +``` +yarn 'react', '16' +yarn 'react-dom', '16' +yarn 'react-router', '^5.0.0' +yarn 'react-router-dom', '^5.0.0' +yarn 'react_ujs', '^2.5.0' +yarn 'jquery', '^3.4.1' # this is only needed if using jquery +yarn 'create-react-class' +``` + +#### Routing + +If using hyper-model you need to mount the Hyperstack engine in the routes file like this: + +```ruby +# config/routes.rb +Rails.application.routes.draw do + # this route should be first in the routes file so it always matches' + mount Hyperstack::Engine => '/hyperstack' # you can use any path you choose + ... +``` + +To directly route from a URL to a component you can use the builting Hyperstack +controller with a route like this: + +```ruby + get "hyperstack-page/(*others)", "hyperstack#comp_name" +``` + +Where `comp_name` is the underscored name of the component you want to mount. I.e. `MyComp` becomes `my_comp`. The `/(*others)` indicates that all routes beginning with +`hyperstack-page/` will be matched, if that is your desired behavior. + +> Note that the engine mount point can be any string you wish but the controller routed to above is always `hyperstack`. + +#### Other Rails Configuration Settings + +Hyperstack will by default set a number of Rails configuration settings. To disable this +set +```ruby + config.hyperstack.auto_config = false +``` +In your Rails application.rb configuration file. + +Otherwise the following settings are automatically applied in test and staging: + +```ruby +# This will prevent any data transmitted by HyperOperation from appearing in logs +config.filter_parameters << :hyperstack_secured_json + +# Add the hyperstack directories +config.eager_load_paths += %W(#{config.root}/app/hyperstack/models) +config.eager_load_paths += %W(#{config.root}/app/hyperstack/models/concerns) +config.eager_load_paths += %W(#{config.root}/app/hyperstack/operations) +config.eager_load_paths += %W(#{config.root}/app/hyperstack/shared) + +# But remove the outer hyperstack directory so rails doesn't try to load its +# contents directly +delete_first config.eager_load_paths, "#{config.root}/app/hyperstack" +``` +but in production we autoload instead of eager load. +```ruby + # add the hyperstack directories to the auto load paths + config.autoload_paths += %W(#{config.root}/app/hyperstack/models) + config.autoload_paths += %W(#{config.root}/app/hyperstack/models/concerns) + config.autoload_paths += %W(#{config.root}/app/hyperstack/operations) + config.autoload_paths += %W(#{config.root}/app/hyperstack/shared) + + # except for the outer hyperstack directory + delete_first config.autoload_paths, "#{config.root}/app/hyperstack" +end +``` diff --git a/docs/rails-installation/prerequisites.md b/docs/rails-installation/prerequisites.md new file mode 100644 index 000000000..61443e8e6 --- /dev/null +++ b/docs/rails-installation/prerequisites.md @@ -0,0 +1,35 @@ +## Prerequisites + +#### Rails + +Hyperstack is currently tested on Rails ~> 5.0 and ~> 6.0, if you are on Rails 4.0 it might be time to upgrade, but that said you probably can manually install Hyperstack on Rails 4.0 and get it working. + +[Rails Install Instructions](http://railsinstaller.org/en) + +#### Yarn + +For a full system install including webpacker for managing javascript assets you will +need yarn. To skip adding webpacker use `hyperstack:install:skip-webpack` when installing Hyperstack. + +[Yarn Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) + +#### - Database + +To fully utilize Hyperstack's capabilities you will be need an SQL database that has an ActiveRecord adapter. If you have a choice we have found Postgresql works best (and it also deploys to Heroku without issue.) If you are new to Rails, then the default Sqlite database (which rails will install) will work fine. +> Why Don't You Support NoSql databases? The biggest reasons are security and effeciency. Hyperstack access policies are based on known table names and attributes, and after commit hooks. Keep in mind that modern DBs (and Hyperstack support the json and jsonb attribute types allowing you to add arbitrary json based data to your database) + +### Creating a New Rails App + +If you don't have an existing Rails app to add Hyperstack to, you can create a new Rails app +with the following command line: + +``` +bundle exec rails new NameOfYourApp -T +``` + +To avoid much pain, do not name your app `Application` as this will conflict with all sorts of +things in Rails and Hyperstack. + +Once you have created the app cd into the newly created directory. + +> The -T option will skip adding minitest directories as Hyperstack prefers RSpec. However if you have an existing app with minitest that is okay too. diff --git a/docs/rails-installation/using-the-installer.md b/docs/rails-installation/using-the-installer.md new file mode 100644 index 000000000..dde69b6a3 --- /dev/null +++ b/docs/rails-installation/using-the-installer.md @@ -0,0 +1,38 @@ +## Installing HyperStack + +* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your gem file +* run `bundle install` +* run `bundle exec rails hyperstack:install` + +> Note: if you want to use the unreleased edge branch your gem specification will be: +> +> ```ruby +> gem 'rails-hyperstack', +> git: 'git://github.com/hyperstack-org/hyperstack.git', +> branch: 'edge', +> glob: 'ruby/*/*.gemspec' +> ``` + +### Start the Rails app + +* `bundle exec foreman start` to start Rails and the Hotloader +* Navigate to `http://localhost:5000/` + +You will see an empty page with the word "App" displayed. + +### Installer Options + +You can control what gets installed with the following options: + +``` +bundle exec rails hyperstack:install:webpack # just add webpack +bundle exec rails hyperstack:install:skip-webpack # all but webpack +bundle exec rails hyperstack:install:hyper-model # just add hyper-model +bundle exec rails hyperstack:install:skip-hyper-model # all but hyper-model +bundle exec rails hyperstack:install:hotloader # just add the hotloader +bundle exec rails hyperstack:install:skip-hotloader # skip the hotloader +``` + +> Note that the `:webpack` and `:skip-webpack` options control whether the installer will +add the webpacker Gem. If webpacker is already installed in the Gemfile then the +installer will always integrate with webpacker. diff --git a/docs/tutorial/intro_to_hyper_components.md b/docs/tutorial/intro_to_hyper_components.md new file mode 100644 index 000000000..e9b4a2632 --- /dev/null +++ b/docs/tutorial/intro_to_hyper_components.md @@ -0,0 +1,132 @@ +#Tutorial: Intro to React + +This tutorial doesn’t assume any existing HyperStack or React knowledge. + +##Before We Start the Tutorial + +> This tutorial is shamelessly stolen for pedagogical reasons from this [React tutorial](https://reactjs.org/tutorial/tutorial.html) + +In this tutorial you will build a small game. Even though its just a small game, the techniques you’ll learn are fundamental to building any HyperStack app, and mastering it will give you a deep understanding of not only Hyper Conponents but also of React. + + +> Tip: This tutorial is designed for people who prefer to learn by doing. If you prefer learning concepts from the ground up, check out our step-by-step guide. You might find this tutorial and the guide complementary to each other. + +The tutorial is divided into several sections: + ++ Setup for the Tutorial will give you a starting point to follow the tutorial. ++ Overview will teach you the fundamentals of HyperStack: Components, Params and State. ++ Completing the game will teach you the most common techniques in Hyperstack development. ++ Adding Time Travel will give you a deeper insight into the unique strengths of HyperStack and the underlying React technologies. + +You don’t have to complete all of the sections at once to get the value out of this tutorial. Try to get as far as you can — even if it’s one or two sections. + +### What Are We Building? + +In this tutorial, we’ll show how to build an interactive tic-tac-toe game with HyperStack. + +###Prerequisites + +We’ll assume that you have some familiarity with HTML and Ruby, but you should be able to follow along even if you’re coming from a different programming language such as Javascript, or if you have familiarit with React. We’ll also assume that you’re familiar with programming concepts like methods (functions), objects, arrays, and to a lesser extent, classes. + +Setup for the Tutorial +There are two ways to complete this tutorial: you can either write the code in your browser, or you can set up a local development environment on your computer. + +Setup Option 1: Write Code in the Browser +This is the quickest way to get started! + +First, open this Starter Code in a new tab. The new tab should display an empty tic-tac-toe game board and React code. We will be editing the React code in this tutorial. + +You can now skip the second setup option, and go to the Overview section to get an overview of React. + +Setup Option 2: Local Development Environment +This is completely optional and not required for this tutorial! + + +Optional: Instructions for following along locally using your preferred text editor +Help, I’m Stuck! +If you get stuck, check out the community support resources. In particular, Reactiflux Chat is a great way to get help quickly. If you don’t receive an answer, or if you remain stuck, please file an issue, and we’ll help you out. + +Overview +Now that you’re set up, let’s get an overview of React! + +What Is React? +React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called “components”. + +React has a few different kinds of components, but we’ll start with React.Component subclasses: + +class ShoppingList extends React.Component { + render() { + return ( +
+

Shopping List for {this.props.name}

+
    +
  • Instagram
  • +
  • WhatsApp
  • +
  • Oculus
  • +
+
+ ); + } +} + +// Example usage: +We’ll get to the funny XML-like tags soon. We use components to tell React what we want to see on the screen. When our data changes, React will efficiently update and re-render our components. + +Here, ShoppingList is a React component class, or React component type. A component takes in parameters, called props (short for “properties”), and returns a hierarchy of views to display via the render method. + +The render method returns a description of what you want to see on the screen. React takes the description and displays the result. In particular, render returns a React element, which is a lightweight description of what to render. Most React developers use a special syntax called “JSX” which makes these structures easier to write. The
syntax is transformed at build time to React.createElement('div'). The example above is equivalent to: + +return React.createElement('div', {className: 'shopping-list'}, + React.createElement('h1', /* ... h1 children ... */), + React.createElement('ul', /* ... ul children ... */) +); +See full expanded version. + +If you’re curious, createElement() is described in more detail in the API reference, but we won’t be using it in this tutorial. Instead, we will keep using JSX. + +JSX comes with the full power of JavaScript. You can put any JavaScript expressions within braces inside JSX. Each React element is a JavaScript object that you can store in a variable or pass around in your program. + +The ShoppingList component above only renders built-in DOM components like
and
  • . But you can compose and render custom React components too. For example, we can now refer to the whole shopping list by writing . Each React component is encapsulated and can operate independently; this allows you to build complex UIs from simple components. + +Inspecting the Starter Code +If you’re going to work on the tutorial in your browser, open this code in a new tab: Starter Code. If you’re going to work on the tutorial locally, instead open src/index.js in your project folder (you have already touched this file during the setup). + +This Starter Code is the base of what we’re building. We’ve provided the CSS styling so that you only need to focus on learning React and programming the tic-tac-toe game. + +By inspecting the code, you’ll notice that we have three React components: + +Square +Board +Game +The Square component renders a single + ); + } +} +Before: + +React Devtools +After: You should see a number in each square in the rendered output. + +React Devtools +View the full code at this point diff --git a/release-notes/1.0.alpha1.5.md b/release-notes/1.0.alpha1.5.md new file mode 100644 index 000000000..26c1bb102 --- /dev/null +++ b/release-notes/1.0.alpha1.5.md @@ -0,0 +1,42 @@ +## 1.0alpha1.5 - 2019-06-19 +### Security ++ [#165](https://github.com/hyperstack-org/hyperstack/issues/165) Secure access to `composed_of` relationships. + +### Added ++ [#114](https://github.com/hyperstack-org/hyperstack/issues/114) Add Polymorphic Models ++ [#186](https://github.com/hyperstack-org/hyperstack/issues/186) Allow components to override built-in event names (i.e. `fires :click`) ++ [#185](https://github.com/hyperstack-org/hyperstack/issues/185) Allow import of es6 modules that have a single component ++ [#183](https://github.com/hyperstack-org/hyperstack/issues/183) Add new Rails 6 active support methods: `extract!` and `index_with` ++ [#180](https://github.com/hyperstack-org/hyperstack/issues/180) `sleep` now returns a promise so it works nicely with Operations ++ [#176](https://github.com/hyperstack-org/hyperstack/issues/176) The `render` callback is now optional. See issue for details. ++ [#168](https://github.com/hyperstack-org/hyperstack/issues/168) Allow custom headers in `ServerOp`s ++ [#160](https://github.com/hyperstack-org/hyperstack/issues/160) Allows for dynamically attaching events: `.on(false || nil)` is ignored. ++ [#159](https://github.com/hyperstack-org/hyperstack/issues/159) Hyperstack.connect behaves nicely if passed a dummy value. ++ [#148](https://github.com/hyperstack-org/hyperstack/issues/148) Rails installer works with existing Rails apps. ++ [#146](https://github.com/hyperstack-org/hyperstack/issues/146) Allow ActiveModel attribute methods to be overridden. + + +### Fixed ++ [#196](https://github.com/hyperstack-org/hyperstack/issues/196) The `empty?` method no longer forces fetch of entire collection ++ [#195](https://github.com/hyperstack-org/hyperstack/issues/195) UI will not update until after all relationships of a destroyed record are completely updated. ++ [#194](https://github.com/hyperstack-org/hyperstack/issues/194) Fetching STI models via scope and finder will now return the same backing record. ++ [#193](https://github.com/hyperstack-org/hyperstack/issues/193) Allow the `super` method in hyper-spec examples. ++ [#192](https://github.com/hyperstack-org/hyperstack/issues/192) Dummy values will be initialized with schema default value. ++ [#191](https://github.com/hyperstack-org/hyperstack/issues/191) Fixed incompatibility between the Router and Legacy style param method. ++ [#181](https://github.com/hyperstack-org/hyperstack/issues/181) Fixed nested class component lookup. ++ [#179](https://github.com/hyperstack-org/hyperstack/issues/179) Once an operation moves to the failed track it now stays on the failed track. ++ [#178](https://github.com/hyperstack-org/hyperstack/issues/178) Resetting system now correctly reinitializes all variables. ++ [#173](https://github.com/hyperstack-org/hyperstack/issues/173) Both sides of a relationship can be new and will get saved properly. ++ [#170](https://github.com/hyperstack-org/hyperstack/issues/170) HyperSpec `pause` method working again. ++ [#169](https://github.com/hyperstack-org/hyperstack/issues/169) Fixes to ActiveRecord model equality test. ++ [#166](https://github.com/hyperstack-org/hyperstack/issues/166) Allow `Element#dom_node` to work with native components. ++ [#164](https://github.com/hyperstack-org/hyperstack/issues/164) Insure state change notification when scopes change remotely. ++ [#163](https://github.com/hyperstack-org/hyperstack/issues/163) Ignore hotloader and hotloader errors during prerendering. ++ [#154](https://github.com/hyperstack-org/hyperstack/issues/154) Stop raising deprecation notices when using `imports` directive. ++ [#153](https://github.com/hyperstack-org/hyperstack/issues/153) `.to_n` working properly on Component classes. ++ [#144](https://github.com/hyperstack-org/hyperstack/issues/144) Timeout if connection between console and server fails. ++ [#143](https://github.com/hyperstack-org/hyperstack/issues/143) `Errors#full_messages` working properly. ++ [#138](https://github.com/hyperstack-org/hyperstack/issues/138) Count of has_many :through relations working properly ++ [#126](https://github.com/hyperstack-org/hyperstack/issues/126) Scopes no longer returning extra `DummyValue`. ++ [#125](https://github.com/hyperstack-org/hyperstack/issues/125) Belongs-to relationships on new records will react to updates to the relationship. ++ [#120](https://github.com/hyperstack-org/hyperstack/issues/120) `ActiveRecord::Base.new?` renamed to `new_record?` (you can still use `new?` or override it) diff --git a/release-notes/1.0.alpha1.6.md b/release-notes/1.0.alpha1.6.md new file mode 100644 index 000000000..8852f2e40 --- /dev/null +++ b/release-notes/1.0.alpha1.6.md @@ -0,0 +1,52 @@ +## 1.0alpha1.6 - 2021-03-21 + +### New Major Features ++ Now compatible with Opal 1.x and Rails 6.x: Tested with Opal ~>1.0 + Rails ~>5.0 and Rails ~>6.0, and Opal ~>0.11 and Rails ~>5.0. ++ You can run Hypermodel connections on Redis (instead of ActiveRecord). This gives about a 10% performance boost, and there will be even better +performance as the Redis adapter is optimized. ++ The `rails-hyperstack` gem now includes a file `hyperstack/server_side_auto_require` that you may require from the `hyperstack.rb` initializer. +This file will add the capability to the Rails ActiveSupport Dependencies to automatically look for files in the matching main app sub directory when loaded first from the `hyperstack` directory. This allows you to leave serverside functionality in the main app subdirectories, and only include definitions relevant to the client side in the `hyperstack` directories. See https://github.com/hyperstack-org/hyperstack/issues/361 for more info. ATM requiring this file will set the Rails autoloader mode to :classic. ++ Complete rewrite of Hyperspec with much improved syntax and many new features including ability to use with any Rack application. See [the docs](https://docs.hyperstack.org/development-workflow/hyper-spec) for details. But don't worry its all backwards compatible with the old syntax. ++ Much more robust gem installer. + + +### Breaking Changes ++ [#350](https://github.com/hyperstack-org/hyperstack/issues/350) Move server side after and every methods to include module. You are only effected if you are using the `after` or `every` methods on the server. ++ You may encounter some breakage due to configuration changes. Rails and the JS world have changed a lot recently, and its hard to insure that the new Gems will work correctly in all situations without some adjustment to your configuration. Please report any issues that you encounter. + +### Security ++ [#205](https://github.com/hyperstack-org/hyperstack/issues/205) Now filters ServerOp params from log files. + +### Added ++ [#379](https://github.com/hyperstack-org/hyperstack/issues/379) If operation dispatch raises an error the operation now fails ++ [#376](https://github.com/hyperstack-org/hyperstack/issues/376) Control Arity Check From HyperSpec ++ [#372](https://github.com/hyperstack-org/hyperstack/issues/372) More flexibility with render block return values ++ [#365](https://github.com/hyperstack-org/hyperstack/issues/365) Added ActiveRecord `increment!` and `decrement!` methods ++ [#364](https://github.com/hyperstack-org/hyperstack/issues/364) Added ActiveRecord `has_and_belongs_to_many` to HyperModel ++ [#356](https://github.com/hyperstack-org/hyperstack/issues/356) Added `json` and `jsonb` ActiveRecord attribute types ++ [#353](https://github.com/hyperstack-org/hyperstack/issues/353) Allow for empty `policy...to(...)` call ++ [#322](https://github.com/hyperstack-org/hyperstack/issues/322) Correctly pass back the return value from observer block ++ [#306](https://github.com/hyperstack-org/hyperstack/issues/306) Relaxed libV8 dependency and removed where possible ++ [#280](https://github.com/hyperstack-org/hyperstack/issues/280) Better error messages for active record failures ++ [#220](https://github.com/hyperstack-org/hyperstack/issues/220) Added shims for browsers not supporting ECMA 6.0 classes ++ [#218](https://github.com/hyperstack-org/hyperstack/issues/218) `on: create` hooks now run BEFORE create not after ++ [#158](https://github.com/hyperstack-org/hyperstack/issues/158) HyperComponent multiple value and `FRAGMENT` returns + + +### Fixed ++ [#380](https://github.com/hyperstack-org/hyperstack/issues/380) Specs now running with Opal `arity_checking` enabled ++ [#375](https://github.com/hyperstack-org/hyperstack/issues/375) Scopes could get out of sync ++ [#358](https://github.com/hyperstack-org/hyperstack/issues/358) Incoming broadcast messages were not working if primary key was not `:id` ++ [#354](https://github.com/hyperstack-org/hyperstack/issues/354) Correctly set react variant in production ++ [#347](https://github.com/hyperstack-org/hyperstack/issues/347) Fixed Rails generator and React import misleading comments ++ [#326](https://github.com/hyperstack-org/hyperstack/issues/326) No longer raises Rails `previous_changes` behavior deprecation notices ++ [#325](https://github.com/hyperstack-org/hyperstack/issues/325) Schema default value conversion now supports strings ++ [#275](https://github.com/hyperstack-org/hyperstack/issues/275) Fixed issue with reflects on associations of sibling STI class ++ [#269](https://github.com/hyperstack-org/hyperstack/issues/269) Fixed: TypeError raised when prerendering. ++ [#215](https://github.com/hyperstack-org/hyperstack/issues/215) Collection `any?` method now accepts args. ++ [#184](https://github.com/hyperstack-org/hyperstack/issues/184) ActiveRecord::Base.find([1,2,3]) returns mutiple records same as AR ++ [#162](https://github.com/hyperstack-org/hyperstack/issues/162) Prevent footer from rendering multiple times on the same page (performance issue) + +### Deprecated ++ [#374](https://github.com/hyperstack-org/hyperstack/issues/374) Support for React 15 Dropped ++ [#373](https://github.com/hyperstack-org/hyperstack/issues/373) `componentWillMount` and friends deprecated from React diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb b/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb index d8f05f365..10744683d 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/rails/component_mount.rb @@ -43,6 +43,9 @@ def top_level_name end def footers + return if @hyperstack_footers_rendered + + @hyperstack_footers_rendered = true Hyperstack::Component::IsomorphicHelpers.prerender_footers(controller) end end diff --git a/ruby/hyper-model/lib/reactive_record/broadcast.rb b/ruby/hyper-model/lib/reactive_record/broadcast.rb index 5c554efb1..b9b03c603 100644 --- a/ruby/hyper-model/lib/reactive_record/broadcast.rb +++ b/ruby/hyper-model/lib/reactive_record/broadcast.rb @@ -9,7 +9,7 @@ def self.after_commit(operation, model) puts "Broadcast aftercommit hook: #{data}" if Hyperstack::Connection.show_diagnostics if !Hyperstack.on_server? && Hyperstack::Connection.root_path - send_to_server(operation, data) rescue nil # fails if server no longer running so ignore + send_to_server(operation, data, model.__synchromesh_update_time) rescue nil # fails if server no longer running so ignore else SendPacket.run(data, operation: operation, updated_at: model.__synchromesh_update_time) end @@ -18,7 +18,7 @@ def self.after_commit(operation, model) raise e unless e.message == "Could not find table 'hyperstack_connections'" end unless RUBY_ENGINE == 'opal' - def self.send_to_server(operation, data) + def self.send_to_server(operation, data, updated_at) salt = SecureRandom.hex authorization = Hyperstack.authorization(salt, data[:channel], data[:broadcast_id]) raise 'no server running' unless Hyperstack::Connection.root_path @@ -27,6 +27,7 @@ def self.send_to_server(operation, data) Hyperstack::Connection.root_path, data, operation: operation, + updated_at: updated_at, salt: salt, authorization: authorization ).tap { |p| raise p.error if p.rejected? } diff --git a/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb b/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb index 6324ed064..78d59adec 100644 --- a/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb +++ b/ruby/rails-hyperstack/lib/generators/hyper/templates/component_template.rb @@ -37,7 +37,7 @@ <%=" "* @indent %> before_unmount do <%=" "* @indent %> # cleanup any thing before component is destroyed -<%=" "* @indent %> # note timers are broadcast receivers are cleaned up +<%=" "* @indent %> # note timers and broadcast receivers are cleaned up <%=" "* @indent %> # automatically <%=" "* @indent %> end diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb index 2bd69bcd8..672aabef8 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator.rb @@ -9,17 +9,6 @@ class InstallGenerator < Rails::Generators::Base class_option 'webpack-only', type: :boolean class_option 'hyper-model-only', type: :boolean - def add_component - if skip_adding_component? - # normally this is handled by the hyper:component - # generator, but if we are skipping it we will check it - # now. - insure_hyperstack_loader_installed - else - generate 'hyper:router App --add-route' - end - end - def add_hotloader return if skip_hotloader? unless Hyperstack.imported? 'hyperstack/hotloader' @@ -37,94 +26,27 @@ def add_hotloader end end - def insure_yarn_loaded - return if skip_webpack? - begin - yarn_version = `yarn --version` - raise Errno::ENOENT if yarn_version.blank? - rescue Errno::ENOENT - raise Thor::Error.new("please insure nodejs is installed and the yarn command is available if using webpacker") - end - end - - def add_webpacker_manifests - return if skip_webpack? - create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT -//app/javascript/packs/client_and_server.js -// these packages will be loaded both during prerendering and on the client -React = require('react'); // react-js library -createReactClass = require('create-react-class'); // backwards compatibility with ECMA5 -History = require('history'); // react-router history library -ReactRouter = require('react-router'); // react-router js library -ReactRouterDOM = require('react-router-dom'); // react-router DOM interface -ReactRailsUJS = require('react_ujs'); // interface to react-rails -// to add additional NPM packages run `yarn add package-name@version` -// then add the require here. - JAVASCRIPT - create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT -//app/javascript/packs/client_only.js -// add any requires for packages that will run client side only -ReactDOM = require('react-dom'); // react-js client side code -jQuery = require('jquery'); // remove if you don't need jQuery -// to add additional NPM packages call run yarn add package-name@version -// then add the require here. - JAVASCRIPT - append_file 'config/initializers/assets.rb' do - <<-RUBY -Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s - RUBY - end - inject_into_file 'config/environments/test.rb', before: /^end/ do - <<-RUBY + def install_webpack + return super unless skip_webpack? - # added by hyperstack installer - config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s - RUBY - end - end - - def add_webpacks - return if skip_webpack? - - yarn 'react', '16' - yarn 'react-dom', '16' - yarn 'react-router', '^5.0.0' - yarn 'react-router-dom', '^5.0.0' - yarn 'react_ujs', '^2.5.0' - yarn 'jquery', '^3.4.1' - yarn 'create-react-class' - end - - def cancel_react_source_import inject_into_initializer( - if skip_webpack? - "Hyperstack.import 'react/react-source-browser' "\ - "# bring in hyperstack's copy of react, comment this out "\ - 'if you bring it in from webpacker' - else - "# Hyperstack.import 'react/react-source-browser' "\ - '# uncomment this line if you want hyperstack to use its copy of react' - end + "Hyperstack.import 'react/react-source-browser' "\ + "# bring in hyperstack's copy of react, comment this out "\ + "if you bring it in from webpacker\n" ) end - def install_webpacker - return if skip_webpack? - - gem "webpacker" unless defined? ::Webpacker - Bundler.with_unbundled_env do - run "bundle install" + def add_component + # add_component AFTER webpack so component generator webpack check works + if skip_adding_component? + # normally this is handled by the hyper:component + # generator, but if we are skipping it we will check it + # now. + insure_hyperstack_loader_installed + check_javascript_link_directory + else + generate 'hyper:router App --add-route' end - `spring stop` - Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails webpacker:install' } - end - - def check_javascript_link_directory - manifest_js_file = Rails.root.join("app", "assets", "config", "manifest.js") - return unless File.exist? manifest_js_file - return unless File.readlines(manifest_js_file).grep(/javascripts \.js/).empty? - - append_file manifest_js_file, "//= link_directory ../javascripts .js\n" end def create_policies_directory @@ -146,9 +68,6 @@ class ApplicationPolicy regulate_all_broadcasts { |policy| policy.send_all } # Allow all changes to models allow_change(to: :all, on: [:create, :update, :destroy]) { true } - # allow remote access to all scopes - i.e. you can count or get a list of ids - # for any scope or relationship - ApplicationRecord.regulate_scope :all end unless Rails.env.production? end RUBY @@ -162,6 +81,12 @@ def move_and_update_application_record unless File.exist? hyper_app_record_file empty_directory Rails.root.join('app', 'hyperstack', 'models') `mv #{rails_app_record_file} #{hyper_app_record_file}` + inject_into_file hyper_app_record_file, before: /^end/, verbose: false do + " # allow remote access to all scopes - i.e. you can count or get a list of ids\n"\ + " # for any scope or relationship\n"\ + " ApplicationRecord.regulate_scope :all unless Hyperstack.env.production?\n" + end + # create_file rails_app_record_file, <<-RUBY # # #{rails_app_record_file} # # the presence of this file prevents rails migrations from recreating application_record.rb @@ -172,6 +97,19 @@ def move_and_update_application_record end end + def turn_on_transport + inject_into_initializer <<-RUBY + +# transport controls how push (websocket) communications are +# implemented. The default is :none. +# Other possibilities are :action_cable, :pusher (see www.pusher.com) +# or :simple_poller which is sometimes handy during system debug. + +Hyperstack.transport = :action_cable # :pusher, :simple_poller or :none + + RUBY + end + def add_engine_route return if skip_hyper_model? route 'mount Hyperstack::Engine => \'/hyperstack\' # this route should be first in the routes file so it always matches' @@ -237,61 +175,5 @@ def new_rails_app? count <= 2 end end - - def inject_into_initializer(s) - file_name = Rails.root.join('config', 'initializers', 'hyperstack.rb') - if File.exist?(file_name) - prepend_to_file(file_name) { "#{s}\n" } - else - create_file file_name, <<-RUBY -#{s} -# server_side_auto_require will patch the ActiveSupport Dependencies module -# so that you can define classes and modules with files in both the -# app/hyperstack/xxx and app/xxx directories. For example you can split -# a Todo model into server and client related definitions and place this -# in `app/hyperstack/models/todo.rb`, and place any server only definitions in -# `app/models/todo.rb`. - -require "hyperstack/server_side_auto_require.rb" - -# set the component base class - -Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' - -# prerendering is default :off, you should wait until your -# application is relatively well debugged before turning on. - -Hyperstack.prerendering = :off # or :on - -# transport controls how push (websocket) communications are -# implemented. The default is :action_cable. -# Other possibilities are :pusher (see www.pusher.com) or -# :simple_poller which is sometimes handy during system debug. - -Hyperstack.transport = :action_cable # or :none, :pusher, :simple_poller - -# add this line if you need jQuery AND ARE NOT USING WEBPACK -# Hyperstack.import 'hyperstack/component/jquery', client_only: true - -# change definition of on_error to control how errors such as validation -# exceptions are reported on the server -module Hyperstack - def self.on_error(operation, err, params, formatted_error_message) - ::Rails.logger.debug( - "\#{formatted_error_message}\\n\\n" + - Pastel.new.red( - 'To further investigate you may want to add a debugging '\\ - 'breakpoint to the on_error method in config/initializers/hyperstack.rb' - ) - ) - end -end if Rails.env.development? - RUBY - end - # whenever we modify the initializer its best to empty the cache, BUT - # we only need to it once per generator execution - run 'rm -rf tmp/cache' unless @cache_emptied_already - @cache_emptied_already = true - end end end diff --git a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb index 6cdf9564e..37fc28e74 100644 --- a/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb +++ b/ruby/rails-hyperstack/lib/generators/hyperstack/install_generator_base.rb @@ -13,6 +13,8 @@ def warnings def create_component_file(template) clear_cache insure_hyperstack_loader_installed + check_javascript_link_directory + added = webpack_check insure_base_component_class_exists @no_help = options.key?('no-help') self.components.each do |component| @@ -26,6 +28,10 @@ def create_component_file(template) "#{@file_name.underscore}.rb") end add_route + return unless added + + say '📦 Webpack integrated with Hyperstack. '\ + 'Add javascript assets to app/javascript/packs/client_only.js and /client_and_server.js 📦', :green end @@ -42,7 +48,7 @@ def insure_hyperstack_loader_installed unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader } require_tree = %r{//=\s+require_tree\s+} if File.foreach(application_js).any? { |l| l =~ require_tree } - inject_into_file 'app/assets/javascripts/application.js', before: require_tree do + inject_into_file 'app/assets/javascripts/application.js', verbose: false, before: require_tree do "//= require hyperstack-loader\n" end else @@ -72,7 +78,7 @@ def insure_hyperstack_loader_installed /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/ Dir.glob(Rails.root.join('app', 'views', '**', '*.erb')) do |file| if File.foreach(file).any? { |l| l =~ application_pack_tag } - inject_into_file file, after: application_pack_tag do + inject_into_file file, verbose: false, after: application_pack_tag do "\n <%= javascript_include_tag 'application' %>" end end @@ -80,6 +86,14 @@ def insure_hyperstack_loader_installed end end + def check_javascript_link_directory + manifest_js_file = Rails.root.join("app", "assets", "config", "manifest.js") + return unless File.exist? manifest_js_file + return unless File.readlines(manifest_js_file).grep(/javascripts \.js/).empty? + + append_file manifest_js_file, "//= link_directory ../javascripts .js\n", verbose: false + end + def insure_base_component_class_exists @component_base_class = options['base-class'] || Hyperstack.component_base_class file_name = Rails.root.join( @@ -90,9 +104,9 @@ def insure_base_component_class_exists def add_to_manifest(manifest, &block) if File.exist? "app/javascript/packs/#{manifest}" - append_file "app/javascript/packs/#{manifest}", &block + append_file "app/javascript/packs/#{manifest}", verbose: false, &block else - create_file "app/javascript/packs/#{manifest}", &block + create_file "app/javascript/packs/#{manifest}", verbose: false, &block end end @@ -121,6 +135,154 @@ def yarn(package, version = nil) return if system("yarn add #{package}#{'@' + version if version}") raise Thor::Error.new("yarn failed to install #{package} with version #{version}") end + + def install_webpack + insure_yarn_loaded + add_webpacker_manifests + add_webpacks + cancel_react_source_import + install_webpacker + end + + def inject_into_initializer(s) + file_name = Rails.root.join('config', 'initializers', 'hyperstack.rb') + if File.exist?(file_name) + prepend_to_file(file_name, verbose: false) { "#{s}\n" } + else + create_file file_name, <<-RUBY +#{s} + +# server_side_auto_require will patch the ActiveSupport Dependencies module +# so that you can define classes and modules with files in both the +# app/hyperstack/xxx and app/xxx directories. For example you can split +# a Todo model into server and client related definitions and place this +# in `app/hyperstack/models/todo.rb`, and place any server only definitions in +# `app/models/todo.rb`. + +require "hyperstack/server_side_auto_require.rb" + +# set the component base class + +Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' + +# prerendering is default :off, you should wait until your +# application is relatively well debugged before turning on. + +Hyperstack.prerendering = :off # or :on + +# add this line if you need jQuery AND ARE NOT USING WEBPACK +# Hyperstack.import 'hyperstack/component/jquery', client_only: true + +# change definition of on_error to control how errors such as validation +# exceptions are reported on the server +module Hyperstack + def self.on_error(operation, err, params, formatted_error_message) + ::Rails.logger.debug( + "\#{formatted_error_message}\\n\\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' + ) + ) + end +end if Rails.env.development? + RUBY + end + # whenever we modify the initializer its best to empty the cache, BUT + # we only need to it once per generator execution + run 'rm -rf tmp/cache' unless @cache_emptied_already + @cache_emptied_already = true + end + + private + + def webpack_check + return unless defined? ::Webpacker + + client_and_server = Rails.root.join("app", "javascript", "packs", "client_only.js") + return if File.exist? client_and_server + + # Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails hyperstack:install:webpack' } + + # say "warning: you are running webpacker, but the hyperstack webpack files have not been created.\n"\ + # " Suggest you run bundle exec rails hyperstack:install:webpack soon.\n"\ + # " Or to avoid this warning create an empty file named app/javascript/packs/client_only.js", + # :red + install_webpack + true + end + + def insure_yarn_loaded + begin + yarn_version = `yarn --version` + raise Errno::ENOENT if yarn_version.blank? + rescue Errno::ENOENT + raise Thor::Error.new("please insure nodejs is installed and the yarn command is available if using webpacker") + end + end + + def add_webpacker_manifests + create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT +//app/javascript/packs/client_and_server.js +// these packages will be loaded both during prerendering and on the client +React = require('react'); // react-js library +createReactClass = require('create-react-class'); // backwards compatibility with ECMA5 +History = require('history'); // react-router history library +ReactRouter = require('react-router'); // react-router js library +ReactRouterDOM = require('react-router-dom'); // react-router DOM interface +ReactRailsUJS = require('react_ujs'); // interface to react-rails +// to add additional NPM packages run `yarn add package-name@version` +// then add the require here. + JAVASCRIPT + create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT +//app/javascript/packs/client_only.js +// add any requires for packages that will run client side only +ReactDOM = require('react-dom'); // react-js client side code +jQuery = require('jquery'); // remove if you don't need jQuery +// to add additional NPM packages call run yarn add package-name@version +// then add the require here. + JAVASCRIPT + append_file 'config/initializers/assets.rb', verbose: false do + <<-RUBY + Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s + RUBY + end + inject_into_file 'config/environments/test.rb', verbose: false, before: /^end/ do + <<-RUBY + + # added by hyperstack installer + config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s + RUBY + end + end + + def add_webpacks + yarn 'react', '16' + yarn 'react-dom', '16' + yarn 'react-router', '^5.0.0' + yarn 'react-router-dom', '^5.0.0' + yarn 'react_ujs', '^2.5.0' + yarn 'jquery', '^3.4.1' + yarn 'create-react-class' + end + + def cancel_react_source_import + inject_into_initializer( + "# Hyperstack.import 'react/react-source-browser' "\ + "# uncomment this line if you want hyperstack to use its copy of react" + ) + end + + def install_webpacker + return if defined?(::Webpacker) + + gem "webpacker" + Bundler.with_unbundled_env do + run "bundle install" + end + `spring stop` + Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails webpacker:install' } + end end end end From 11b24d4e46e24676feb20bf581ed3873e2bea669 Mon Sep 17 00:00:00 2001 From: catmando Date: Wed, 17 Mar 2021 17:22:34 -0400 Subject: [PATCH 207/307] updated docs --- docs/SUMMARY.md | 2 +- docs/rails-installation/{other_details.md => other-details.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/rails-installation/{other_details.md => other-details.md} (100%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 4f3c9f354..be343b4f7 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -2,7 +2,7 @@ * [Welcome](README.md) * [Rails Installation and Configuration](rails-installation/README.md) - * [Prerequisites](rails-installation/prerequistes.md) + * [Prerequisites](rails-installation/prerequisites.md) * [Using the Hyperstack Installer](rails-installation/using-the-installer.md) * [Using the Generators](rails-installation/generators.md) * [File Structure](rails-installation/file-structure.md) diff --git a/docs/rails-installation/other_details.md b/docs/rails-installation/other-details.md similarity index 100% rename from docs/rails-installation/other_details.md rename to docs/rails-installation/other-details.md From 8f33f3bedbaee3cd5e3b762b72eb814494526b4f Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 18 Mar 2021 11:43:12 -0400 Subject: [PATCH 208/307] doc updates plus fix to react_wrapper --- docs/README.md | 13 +------------ docs/rails-installation/README.md | 2 +- docs/rails-installation/file-structure.md | 4 ++-- docs/rails-installation/generators.md | 4 ++-- docs/rails-installation/using-the-installer.md | 10 +++++++++- .../hyperstack/internal/component/react_wrapper.rb | 5 ++++- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/README.md b/docs/README.md index 81826a073..8ffe99740 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,7 +33,7 @@ Our website serves as a Hyperstack example application. All the doc content is l You can be up and running in **less than 5 minutes**. Just follow the simple setup guide for a new Rails application all correctly configured and ready to go with Hyperstack. -* Setup and Installation: https://docs.hyperstack.org/installation/man-installation +* Setup and Installation: https://docs.hyperstack.org/rails-installation Beyond the installation we strongly suggest new developers work trough the [todo tutorial](https://docs.hyperstack.org/tutorial). As it gives a minimal understanding of the Hyperstack framework. @@ -66,14 +66,3 @@ More specifically, we have a [Feature Matrix](https://github.com/hyperstack-org/ ## License Hyperstack is developed and released under the MIT License. See the [LICENSE](https://github.com/hyperstack-org/hyperstack/blob/edge/LICENSE) file for further details. - -## History - -Hyperstack is an evolution of [Ruby-Hyperloop](https://github.com/ruby-hyperloop). We decided to rename the project to drop the Ruby suffix and also took the opportunity to simplify the repos and project overall. -Hyperloop was started by the developers of the reactrb gem. - -* Old website: [http://ruby-hyperloop.org/](http://ruby-hyperloop.org/) -* Old Github: [https://github.com/ruby-hyperloop](https://github.com/ruby-hyperloop) -* Legacy branch: [https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy](https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy) -* Legacy install script: [https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy/install](https://github.com/hyperstack-org/hyperstack/tree/hyperloop-legacy/install) - diff --git a/docs/rails-installation/README.md b/docs/rails-installation/README.md index e88322214..be1494bb1 100644 --- a/docs/rails-installation/README.md +++ b/docs/rails-installation/README.md @@ -4,7 +4,7 @@ The easiest way to get the full benefit of Hyperstack is to integrate it with a Adding Hyperstack to your existing Rails App is as simple as adding the gem and running the installer. -Read on to make sure you have the necessary prerequisites. +Continue to the next section to make sure you have the necessary prerequisites on your machine: [Prerequisites](https://docs.hyperstack.org/rails-installation/prerequisites) > #### Why Rails? > >Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality. diff --git a/docs/rails-installation/file-structure.md b/docs/rails-installation/file-structure.md index e822b729f..5a2cfc528 100644 --- a/docs/rails-installation/file-structure.md +++ b/docs/rails-installation/file-structure.md @@ -16,12 +16,12 @@ In addition there are configuration settings in existing Rails files that are ex ### The `/app/hyperstack/` Directory -Here lives all your Hyperstack code. Some of the subdirectories are *isomorphic* meaning the code is shared between the client and the server, other directories are client only. +Here lives all your Hyperstack code that will run on the client. Some of the subdirectories are *isomorphic* meaning the code is shared between the client and the server, other directories are client only. Within the `hyperstack` directory there will be the following sub-directories: + `components` *(client-only)* is where your components live. - Following Rails conventions a component with a class of Bar::None::FooManchu should be in a file named `components/bar/none/foo_manchu.rb` + Following Rails conventions a component with a class of `Bar::None::FooManchu` should be in a file named `components/bar/none/foo_manchu.rb` + `models` *(isomorphic)* is where ActiveRecord models are shared with the client. More on this below. diff --git a/docs/rails-installation/generators.md b/docs/rails-installation/generators.md index dbb8e6872..776091b0b 100644 --- a/docs/rails-installation/generators.md +++ b/docs/rails-installation/generators.md @@ -26,10 +26,10 @@ bundle exec rails g hyper:component ComponentName #### File directories and Name Spacing Components -The above will create a new class definition for `ComponentName` in a file named `component_name.rb` and place it in +The above will create a new class definition for `ComponentName` in a file named `component_name.rb` and place it in the `app/hyperstack/components/` directory. The component may be name spaced and will be placed in the appropriate subdirectory. I.e. `Foo::BarSki` will generate -`app/hyperstack/components/foo/barski.rb` +`app/hyperstack/components/foo/bar_ski.rb` #### The `--no-help` flag diff --git a/docs/rails-installation/using-the-installer.md b/docs/rails-installation/using-the-installer.md index dde69b6a3..430a8dce0 100644 --- a/docs/rails-installation/using-the-installer.md +++ b/docs/rails-installation/using-the-installer.md @@ -20,6 +20,14 @@ You will see an empty page with the word "App" displayed. +Open your editor and find the file `/app/hyperstack/components/app.rb` + +Change the `'App'` to `'Hello World'` and save the file. + +You should see the page on the browser change to "Hello World" + +You are in business! + ### Installer Options You can control what gets installed with the following options: @@ -35,4 +43,4 @@ bundle exec rails hyperstack:install:skip-hotloader # skip the hotloader > Note that the `:webpack` and `:skip-webpack` options control whether the installer will add the webpacker Gem. If webpacker is already installed in the Gemfile then the -installer will always integrate with webpacker. +installer will always integrate with webpacker. diff --git a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb index 4a8c30612..bc0db975c 100644 --- a/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb +++ b/ruby/hyper-component/lib/hyperstack/internal/component/react_wrapper.rb @@ -30,6 +30,7 @@ def self.import_native_component(opal_class, native_class) def self.eval_native_react_component(name) component = `eval(name)` raise "#{name} is not defined" if `#{component} === undefined` + component = `component.default` if `component.__esModule` is_component_class = `#{component}.prototype !== undefined` && (`!!#{component}.prototype.isReactComponent` || @@ -43,9 +44,11 @@ def self.eval_native_react_component(name) def self.native_react_component?(name = nil) return false unless name + eval_native_react_component(name) true - rescue + # Exception to be compatible with all versions of opal + rescue Exception # rubocop:disable Lint/RescueException false end From 59ffc322fa173c05e4e4304445bcb4539cfdf98e Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 18 Mar 2021 12:16:16 -0400 Subject: [PATCH 209/307] attempt to get folding into gitbook --- docs/book.json | 3 +++ docs/client-dsl/html-css.md | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 docs/book.json diff --git a/docs/book.json b/docs/book.json new file mode 100644 index 000000000..ed92e2c6e --- /dev/null +++ b/docs/book.json @@ -0,0 +1,3 @@ +{ + "plugins": ["folding-content"] +} diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index eae83d52f..8999620b2 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -49,9 +49,7 @@ end The following HTML and SVG elements are available: -
    -HTML Tags - +{% fold summary="He wishes for the Cloths of Heaven", open=false, style="background: #fafafa;padding: 10px 20px;" %} ``` A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON @@ -75,7 +73,7 @@ U UL VAR VIDEO WBR ``` -
    +{% endfold %}
    SVG Tags From 8c4de69fb30cc29f7485bb55f8e2df87d18991ca Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 18 Mar 2021 13:17:13 -0400 Subject: [PATCH 210/307] updated docs to try to get some basic folding working --- docs/client-dsl/html-css.md | 46 +++++++++++++++++++++++++++---- docs/rails-installation/README.md | 12 ++++++-- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index 8999620b2..ba0b876fa 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -49,7 +49,31 @@ end The following HTML and SVG elements are available: -{% fold summary="He wishes for the Cloths of Heaven", open=false, style="background: #fafafa;padding: 10px 20px;" %} +
    HTML Tags
    
    +A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO  
    +B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON  
    +CANVAS CAPTION CITE CODE COL COLGROUP  
    +DATA DATALIST DD DEL DETAILS DFN DIALOG DIV DL DT  
    +EM EMBED  
    +FIELDSET FIGCAPTION FIGURE FOOTER FORM  
    +H1 H2 H3 H4 H5 H6 HEAD HEADER HR HTML  
    +I IFRAME IMG INPUT INS  
    +KBD KEYGEN  
    +LABEL LEGEND LI LINK  
    +MAIN MAP MARK MENU MENUITEM META METER  
    +NAV NOSCRIPT  
    +OBJECT OL OPTGROUP OPTION OUTPUT  
    +P PARAM PICTURE PRE PROGRESS  
    +Q  
    +RP RT RUBY  
    +S SAMP SCRIPT SECTION SELECT SMALL SOURCE SPAN STRONG STYLE SUB SUMMARY SUP  
    +TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK  
    +U UL  
    +VAR VIDEO  
    +WBR
    +
    + + -
    -SVG Tags +
    SVG Tags
    
    +CIRCLE CLIPPATH  
    +DEFS  
    +ELLIPSE  
    +G  
    +LINE LINEARGRADIENT  
    +MASK  
    +PATH PATTERN POLYGON POLYLINE  
    +RADIALGRADIENT RECT  
    +STOP  
    +SVG  
    +TEXT TSPAN
    +
    + ### HTML parameters diff --git a/docs/rails-installation/README.md b/docs/rails-installation/README.md index be1494bb1..e56d70b50 100644 --- a/docs/rails-installation/README.md +++ b/docs/rails-installation/README.md @@ -4,9 +4,14 @@ The easiest way to get the full benefit of Hyperstack is to integrate it with a Adding Hyperstack to your existing Rails App is as simple as adding the gem and running the installer. -Continue to the next section to make sure you have the necessary prerequisites on your machine: [Prerequisites](https://docs.hyperstack.org/rails-installation/prerequisites) -> #### Why Rails? -> +Continue to the next section to make sure you have the necessary prerequisites on your machine. + +
    Why Rails?
    +

    Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality.

    People sometimes balk at Rails because when they see the huge number of files and directories generated by the Rails installer, it looks crazy, complex, and ineffecient. Keep in mind that this has very little if any impact on your applications performance, and when developing code 90% of your time will be spent in the following directories: app/models and app/hyperstack. The rest of the files are there to hold configuration files, and seldom used content, so they have a place out of the way of your main development activities.

    Developers often believe that Rails modules like ActionController and ActiveRecord while powerful are slow. In the case of Hyperstack this is largely irrelevant since one of our goals is to offload as much work to the client as possible. For example rather than have a multitude of controllers delivering different page views and updates, your client side Hyperstack code is now responsible for that. The role of the server becomes the central database, and the place where secure operations are executed (such as sending mail, authenticating users etc.)

    How about other Rack Frameworks

    But still you may have specific needs for a lighter weight system, or have an existing Sinatra app (for example) that you would like to use with Hyperstack. For now we will say its in the plan, and its just a matter of time. If you are interested leave a comment on this issue: https://github.com/hyperstack-org/hyperstack/issues/340

    +
    + + + From 959c22446eeb10aaa27642202398c2961cd3fc23 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 18 Mar 2021 13:29:02 -0400 Subject: [PATCH 211/307] more doc fixes --- docs/SUMMARY.md | 1 + docs/client-dsl/html-css.md | 52 +++++-------------------------- docs/rails-installation/README.md | 9 ++++-- 3 files changed, 15 insertions(+), 47 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index be343b4f7..41836bd18 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -7,6 +7,7 @@ * [Using the Generators](rails-installation/generators.md) * [File Structure](rails-installation/file-structure.md) * [Other Rails Configuration Details](rails-installation/other-details.md) + * [Why Rails and Other Frameworks](rails-installation/why-rails.md) * [Client DSL](client-dsl/README.md) * [HTML & CSS DSL](client-dsl/html-css.md) * [Component DSL](client-dsl/components.md) diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index ba0b876fa..f37fb320f 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -49,7 +49,8 @@ end The following HTML and SVG elements are available: -
    HTML Tags
    
    +
    +HTML Tags
    
     A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO  
     B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON  
     CANVAS CAPTION CITE CODE COL COLGROUP  
    @@ -71,35 +72,12 @@ TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK
     U UL  
     VAR VIDEO  
     WBR
    -
    +
    +
    - -
    SVG Tags
    
    +
    +SVG Tags
    
     CIRCLE CLIPPATH  
     DEFS  
     ELLIPSE  
    @@ -111,23 +89,9 @@ RADIALGRADIENT RECT
     STOP  
     SVG  
     TEXT TSPAN
    -
    +
    +
    - ### HTML parameters diff --git a/docs/rails-installation/README.md b/docs/rails-installation/README.md index e56d70b50..1a1fb6e63 100644 --- a/docs/rails-installation/README.md +++ b/docs/rails-installation/README.md @@ -6,12 +6,14 @@ Adding Hyperstack to your existing Rails App is as simple as adding the gem and Continue to the next section to make sure you have the necessary prerequisites on your machine. -
    Why Rails?
    +
    +Why Rails?

    Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality.

    People sometimes balk at Rails because when they see the huge number of files and directories generated by the Rails installer, it looks crazy, complex, and ineffecient. Keep in mind that this has very little if any impact on your applications performance, and when developing code 90% of your time will be spent in the following directories: app/models and app/hyperstack. The rest of the files are there to hold configuration files, and seldom used content, so they have a place out of the way of your main development activities.

    Developers often believe that Rails modules like ActionController and ActiveRecord while powerful are slow. In the case of Hyperstack this is largely irrelevant since one of our goals is to offload as much work to the client as possible. For example rather than have a multitude of controllers delivering different page views and updates, your client side Hyperstack code is now responsible for that. The role of the server becomes the central database, and the place where secure operations are executed (such as sending mail, authenticating users etc.)

    How about other Rack Frameworks

    But still you may have specific needs for a lighter weight system, or have an existing Sinatra app (for example) that you would like to use with Hyperstack. For now we will say its in the plan, and its just a matter of time. If you are interested leave a comment on this issue: https://github.com/hyperstack-org/hyperstack/issues/340

    - + +{% endfold %} From 5e7b24b31898a585d708bd58efc1e9cff12fa525 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 19 Mar 2021 05:27:12 -0400 Subject: [PATCH 212/307] more doc updates --- docs/.gitignore | 2 + docs/SUMMARY.md | 8 +- docs/client-dsl/component-basics.md | 107 ++++++ ...nents.md => component-details-original.md} | 149 ++------ docs/client-dsl/component-details.md | 360 ++++++++++++++++++ docs/client-dsl/html-css.md | 65 +--- docs/client-dsl/notes.md | 174 +++++++++ docs/client-dsl/predefined-tags.md | 41 ++ docs/rails-installation/README.md | 21 +- docs/rails-installation/file-structure.md | 18 +- docs/rails-installation/generators.md | 46 +-- docs/rails-installation/other-details.md | 19 +- docs/rails-installation/prerequisites.md | 11 +- .../routing-and-mounting-components.md | 86 +++++ .../rails-installation/using-the-installer.md | 6 +- docs/rails-installation/why-rails.md | 11 + ruby/rails-hyperstack/.gitignore | 1 + 17 files changed, 884 insertions(+), 241 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/client-dsl/component-basics.md rename docs/client-dsl/{components.md => component-details-original.md} (68%) create mode 100644 docs/client-dsl/component-details.md create mode 100644 docs/client-dsl/notes.md create mode 100644 docs/client-dsl/predefined-tags.md create mode 100644 docs/rails-installation/routing-and-mounting-components.md create mode 100644 docs/rails-installation/why-rails.md diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..df6ce0e64 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_book/ +node_modules/ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 41836bd18..45205f0fd 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -6,11 +6,13 @@ * [Using the Hyperstack Installer](rails-installation/using-the-installer.md) * [Using the Generators](rails-installation/generators.md) * [File Structure](rails-installation/file-structure.md) + * [Routing and Mounting Components](rails-installation/routing-and-mounting-components.md) * [Other Rails Configuration Details](rails-installation/other-details.md) - * [Why Rails and Other Frameworks](rails-installation/why-rails.md) + * [Why Rails? Other Frameworks?](rails-installation/why-rails.md) * [Client DSL](client-dsl/README.md) * [HTML & CSS DSL](client-dsl/html-css.md) - * [Component DSL](client-dsl/components.md) + * [Component DSL](client-dsl/component-basics.md) + * [Component Children, Keys and Fragments](client-dsl/component-details.md) * [Lifecycle Methods](client-dsl/lifecycle-methods.md) * [State](client-dsl/state.md) * [Event Handlers](client-dsl/event-handlers.md) @@ -19,6 +21,8 @@ * [Stores](client-dsl/hyper-store.md) * [Elements and Rendering](client-dsl/elements-and-rendering.md) * [Further Reading](client-dsl/further-reading.md) + * [List of Predefined Tags and Components](client-dsl/predefined-tags.md) + * [Notes](client-dsl/notes.md) * [Isomorphic DSL](isomorphic-dsl/README.md) * [Isomorphic Models](isomorphic-dsl/hyper-model.md) * [Isomorphic Operations](isomorphic-dsl/hyper-operation.md) diff --git a/docs/client-dsl/component-basics.md b/docs/client-dsl/component-basics.md new file mode 100644 index 000000000..e5af946a6 --- /dev/null +++ b/docs/client-dsl/component-basics.md @@ -0,0 +1,107 @@ +# HyperComponent DSL - Basics + +The Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface. + +The DSL has the following major areas: + +* The `HyperComponent` class +* HTML DSL elements +* Component Lifecycle Methods \(`before_mount`, `after_mount`, `after_update`\) +* The `param` and `render` methods +* Event handlers +* Miscellaneous methods + +## Defining a Component + +Hyperstack Components are Ruby classes that inherit from the `HyperComponent` base class: + +```ruby +class MyComponent < HyperComponent + ... +end +``` +> **[More on the HyperComponent base class](notes.html#the-hypercomponent-base-class)** + +## The `render` Callback + +At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components](notes.html#abstract-and-concrete-components)** + +```ruby +class Component < HyperComponent + render do + DIV { } # render an empty div + end +end +``` +> The code between the `do` and `end` is called a block. **[More here...](notes.html#blocks-in-ruby)** + +To save a little typing you can also specify the top level element to be rendered: + +```ruby +class Component < HyperComponent + render(DIV, class: 'my-special-class') do + # everything will be rendered in a div + end +end +``` + +To render a component, you reference its class name as a method call from another component. This creates a new instance, passes any parameters and proceeds with the component lifecycle. + +```ruby +class FirstComponent < HyperComponent + render do + NextComponent() # ruby syntax requires either () or {} following the class name + end +end +``` + +Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method. + +> The one exception to using `new` is within a spec to create a "headless" component in order to access its internal state and methods. + +### Invoking Components + +> Note: when invoking a component **you must have** a \(possibly empty\) parameter list or \(possibly empty\) block. +> ```ruby +MyCustomComponent() # ok +MyCustomComponent {} # ok +MyCustomComponent # <--- breaks +> ``` + +## Multiple Components + +So far, we've looked at how to write a single component to display data. Next let's examine how components are combined to build an application. + +By building modular components that reuse other components with well-defined interfaces you can _separate the different concerns_ of your app. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain. + +### Composition Example + +Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API. + +```ruby +class Avatar < HyperComponent + param :user_name + + render(DIV) do + # for each param a method with the same name is defined + ProfilePic(user_name: user_name) + ProfileLink(user_name: user_name) + end +end + +class ProfilePic < HyperComponent + param :user_name + + # note that in Ruby blocks can use do...end or { ... } + render { IMG(src: "https://graph.facebook.com/#{user_name}/picture") } +end + +class ProfileLink < HyperComponent + param :user_name + render do + A(href: "https://www.facebook.com/#{user_name}") do + user_name + end + end +end +``` diff --git a/docs/client-dsl/components.md b/docs/client-dsl/component-details-original.md similarity index 68% rename from docs/client-dsl/components.md rename to docs/client-dsl/component-details-original.md index d69bd35c3..a7d74023e 100644 --- a/docs/client-dsl/components.md +++ b/docs/client-dsl/component-details-original.md @@ -1,114 +1,27 @@ -# Hyperstack Component DSL +## Component Ownership, Children, Keys, and Fragments -The Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface. - -The DSL has the following major areas: - -* The `HyperComponent` class -* HTML DSL elements -* Component Lifecycle Methods \(`before_mount`, `after_mount`, `after_update`\) -* The `param` and `render` methods -* Event handlers -* Miscellaneous methods - -## HyperComponent - -By convention your Hyperstack Components will inherit from the `HyperComponent` class, which typically would look like this: - -```ruby -class HyperComponent - # All component classes must include Hyperstack::Component - include Hyperstack::Component - # The Observable module adds state handling - include Hyperstack::State::Observable - # The following turns on the new style param accessor - # i.e. param :foo is accessed by the foo method - param_accessor_style :accessors -end - -# and is used like this: - -class AnotherComponent < HyperComponent - ... -end -``` - -Having an Application wide HyperComponent class allows you to modify component behavior on an application basis, similar to the way Rails uses `ApplicationRecord` and `ApplicationController` classes. - -## The `render` Callback - -At a minimum every component class must define a `render` block which returns one or more child elements. Those children may in turn have an arbitrarily deep structure. - -```ruby -class Component < HyperComponent - render do - DIV { } # render an empty div - end -end -``` - -To save a little typing you can also specify the top level element to be rendered: - -```ruby -class Component < HyperComponent - render(DIV, class: 'my-special-class') do - # everything will be rendered in a div - end -end -``` - -To render a component, you reference its class name as a method call from another component. This creates a new instance, passes any parameters and proceeds with the component lifecycle. - -```ruby -class FirstComponent < HyperComponent - render do - NextComponent() # ruby syntax requires either () or {} following the class name - end -end -``` - -Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method. - -> The one exception to using `new` is within a spec to create a "headless" component in order to access its internal state and methods. - -### Invoking Components - -> Note: when invoking a component **you must have** a \(possibly empty\) parameter list or \(possibly empty\) block. -> ```ruby -MyCustomComponent() # ok -MyCustomComponent {} # ok -MyCustomComponent # <--- breaks -> ``` - -## Multiple Components - -So far, we've looked at how to write a single component to display data. Next let's examine one of React's finest features: composability. - -### Motivation: Separation of Concerns - -By building modular components that reuse other components with well-defined interfaces, you get much of the same benefits that you get by using functions or classes. Specifically you can _separate the different concerns_ of your app however you please simply by building new components. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain. +### Ownership -### Composition Example +In the following example (copied from the previous section) instances of `Avatar` _own_ instances of `ProfilePic` and `ProfileLink`. In Hyperstack (like React), **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is _owned by_ `Y`. As will be discussed later a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent. -Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API. +It's important to draw a distinction between the owner-owned-by relationship and the parent-child relationship. The owner-owned-by relationship is specific to Hyperstack/React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `DIV`, `ProfilePic` and `ProfileLink` instances, and `DIV` is the **parent** \(but not owner\) of the `ProfilePic` and `ProfileLink` instances. ```ruby class Avatar < HyperComponent param :user_name - render(DIV) do - # for each param a method with the same name is defined - ProfilePic(user_name: user_name) - ProfileLink(user_name: user_name) + render do # this can be shortened to render(DIV) do - see the previous section + DIV do + ProfilePic(user_name: user_name) # belongs to Avatar, owned by DIV + ProfileLink(user_name: user_name) # belongs to Avatar, owned by DIV + end end end class ProfilePic < HyperComponent param :user_name - - render do - IMG(src: "https://graph.facebook.com/#{user_name}/picture") - end + # note that in Ruby blocks can use do...end or { ... } + render { IMG(src: "https://graph.facebook.com/#{user_name}/picture") } end class ProfileLink < HyperComponent @@ -121,38 +34,42 @@ class ProfileLink < HyperComponent end ``` -### Ownership - -In the above example, instances of `Avatar` _own_ instances of `ProfilePic` and `ProfileLink`. In React, **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is _owned by_ `Y`. As discussed earlier, a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent. - -It's important to draw a distinction between the owner-ownee relationship and the parent-child relationship. The owner-ownee relationship is specific to React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `div`, `ProfilePic` and `ProfileLink` instances, and `div` is the **parent** \(but not owner\) of the `ProfilePic` and `ProfileLink` instances. - ### Children -When you create a React component instance, you can include additional React components or JavaScript expressions between the opening and closing tags like this: +Parent components may be provided a *block* (an anonymous Ruby function or callback) that the parent will use to generate its children. Inside +the block can be abitrary Ruby code that can generate any number of children *Elements* that will be rendered by the parent. ```ruby -Parent { Child() } + DIV do + # this DIV will have two children + ProfilePic(user_name: user_name) + ProfileLink(user_name: user_name) + end +# or + A(href: "https://www.facebook.com/#{user_name}") do + # the anchor tag will have a single child the user_name string + user_name + end ``` -`Parent` can iterate over its children by accessing its `children` method. +Note that as well as calling other Components (such as ProfilePic) the child block may return a string that will used as the single child content of +parent. If the child block has already generated a other components this final string will be ignored. + ### Child Reconciliation -**Reconciliation is the process by which React updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass the items will be completely re-rendered: +**Reconciliation is the process by which the underlying React engine updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass the items will be regenerated and then merged into the DOM by React. ```ruby param :items render do items.each do |item| - P do - item[:text] - end + P { item[:text] } end end ``` -What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`? Intuitively, the paragraph `

    foo

    ` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the _order_ of the children. +What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`? Intuitively, the paragraph `

    foo

    ` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the _order_ of the children. ### Dynamic Children @@ -171,8 +88,7 @@ The situation gets more complicated when the children are shuffled around \(as i When React reconciles the keyed children, it will ensure that any child with `key` will be reordered \(instead of clobbered\) or destroyed \(instead of reused\). -The `key` should _always_ be supplied directly to the components in the array, not to the container HTML child of each component in the array: - +> For best results the `key` is supplied at highest level possible. ```ruby # WRONG! class ListItemWrapper < HyperComponent @@ -193,7 +109,6 @@ class MyComponent < HyperComponent end end ``` - ```ruby # CORRECT class ListItemWrapper < HyperComponent @@ -215,7 +130,9 @@ class MyComponent < HyperComponent end ``` -> The `to_key` method: Any ruby object can be a key. Under the hood the HyperComponent DSL will call the object's `to_key` method which will respond with a unique value representing the object. For example if you pass an ActiveRecord model instance as a key, the result will be the database id of the model. +### The `to_key` method + +Any ruby object can be a key. Under the hood Hyperstack will call the object's `to_key` method which will respond with an appropriate unique value representing the object. For example if you pass an ActiveRecord model instance as a key, the result will be the database id of the model. ### The children and render methods diff --git a/docs/client-dsl/component-details.md b/docs/client-dsl/component-details.md new file mode 100644 index 000000000..c4d1ba04e --- /dev/null +++ b/docs/client-dsl/component-details.md @@ -0,0 +1,360 @@ +## Children, Keys, and Fragments + +### Children + +Components often have child components. If you consider HTML tags like `DIV`, `UL`, and `TABLE` +you will see you are already familiar with this concept: + +```ruby +DIV(id: 1) do + SPAN(class: :span_1) { 'hi' } + SPAN(class: :span_2) { 'there' } +end +``` +Here we have a `DIV` that receives one param, an id equal to 1 *and* has two child *elements* - the two spans. + +The `SPAN`s each have one param (its class) and has one child *element* - a string to render. + +Hopefully the DSL is intuitive to read, and you can see that this will generate the following HTML: +```HTML +
    hithere
    +``` + +### Dynamic Children + +Children do not have to be statically generated. Let's sort a string of text +into individual word counts and display it in a list: + +```ruby +# assume text is a string of text +UL do + word_count(text).each_with_index do |word, count| + LI { "#{count} - #{word}" } + end +end +``` +Here we don't determine the actual number or contents of the `LI` children until runtime. + +>**[The `word_count` method...](notes.html#word-count-method)** + +>Dynamically generating components creates a new concept called ownership. **[More here...](notes.html#ownership)** + +### Keys + +In the above example what would happen if the contents of `text` were dynamically changing? For +example if it was associated with a text box that user was typing into, and we updated `text` +whenever a word was entered. + +In this case as the user typed new words, the `word_count` would be updated and the list would change. +However actually only the contents of one of list items (`LI` blocks) would actually change, and +perhaps the sort order. We don't need to redraw the whole list, just the one list item that changed, +and then perhaps shuffle two of the items. This is going to be much faster than redrawing the whole +list. + +Like React, Hyperstack provides a special key param that you can identify child elements so that the +rendering engine will know that while the content and order may change on some children, it can easily +identify the ones that are the same: + +```ruby + LI(key: word) { "#{count} - #{word}"} +``` + +You don't have to stress out too much about keys, its easy to add them later. Just keep the concept in +mind when you are generating long lists, tables, and divs with many children. + +> **[More on how Hyperstack generates keys...](notes.html#generating-keys)** + + + +### Child Reconciliation + +**Reconciliation is the process by which the underlying React engine updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass the items will be regenerated and then merged into the DOM by React. + +```ruby +param :items +render do + items.each do |item| + P { item[:text] } + end +end +``` + +What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`? Intuitively, the paragraph `

    foo

    ` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the _order_ of the children. + +### Dynamic Children + +The situation gets more complicated when the children are shuffled around \(as in search results\) or if new components are added onto the front of the list \(as in streams\). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a `key`: + +```ruby + param :results, type: [Hash] # each result is a hash of the form {id: ..., text: ....} + render do + OL do + results.each do |result| + LI(key: result[:id]) { result[:text] } + end + end + end +``` + +When React reconciles the keyed children, it will ensure that any child with `key` will be reordered \(instead of clobbered\) or destroyed \(instead of reused\). + +### Rendering Children + +A component's `children` method returns an enumerable that is used to access the *unrendered* children of a component. The children can then be rendered +using the `render` method which will merge any additional parameters and +render the child. + +```ruby +class Indenter < HyperComponent + render(DIV) do + IndentEachLine(by: 10) do # see IndentEachLine below + DIV {"Line 1"} + DIV {"Line 2"} + DIV {"Line 3"} + end + end +end + +class IndentEachLine < HyperComponent + param by: 20, type: Integer + + render(DIV) do + children.each_with_index do |child, i| + child.render(style: {"margin-left" => by*i}) + end + end +end +``` + +### Rendering Multiple Values and the FRAGMENT component + +A render block may generate multiple values. React assumes when a Component generates multiple items, the item order and quantity may +change over time and so will give a warning unless each element has a key: + +```ruby +class ListItems < HyperComponent + render do + # without the keys you would get a warning + LI(key: 1) { 'item 1' } + LI(key: 2) { 'item 2' } + LI(key: 3) { 'item 3' } + end +end + +# somewhere else: + UL do + ListItems() + end +``` + +If you are sure that the order and number of elements will not change over time you may wrap the items in the `FRAGMENT` pseudo component: + +```ruby +class ListItems < HyperComponent + render(FRAGMENT) do + LI { 'item 1' } + LI { 'item 2' } + LI { 'item 3' } + end +end +``` + +The only param that FRAGMENT may take is a key, which is useful if there will be multiple fragments being merged at some higher level. + + +### Data Flow + +In React, data flows from owner to owned component through the params as discussed above. This is effectively one-way data binding: owners bind their owned component's param to some value the owner has computed based on its `params` or `state`. Since this process happens recursively, data changes are automatically reflected everywhere they are used. + +### Stores + +Managing state between components is best done using Stores as many Components can access one store. This saves passing data between Components. Please see the [Store documentation](https://docs.hyperstack.org/client-dsl/hyper-store) for details. + +### Reusable Components + +When designing interfaces, break down the common design elements \(buttons, form fields, layout components, etc.\) into reusable components with well-defined interfaces. That way, the next time you need to build some UI, you can write much less code. This means faster development time, fewer bugs, and fewer bytes down the wire. + +## Params + +The `param` method gives _read-only_ access to each of the scalar params passed to the Component. Params are accessed as instance methods on the Component. + +Within a React Component the `param` method is used to define the parameter signature of the component. You can think of params as the values that would normally be sent to the instance's `initialize` method, but with the difference that a React Component gets new parameters when it is re-rendered. + +Note that the default value can be supplied either as the hash value of the symbol, or explicitly using the `:default_value` key. + +Examples: + +```ruby +param :foo # declares that we must be provided with a parameter foo when the component is instantiated or re-rerendered. +param :foo, alias: :something # the alias name will be used for the param (instead of Foo) +param :foo => "some default" # declares that foo is optional, and if not present the value "some default" will be used. +param foo: "some default" # same as above using ruby 1.9 JSON style syntax +param :foo, default: "some default" # same as above but uses explicit default key +param :foo, type: String # foo is required and must be of type String +param :foo, type: [String] # foo is required and must be an array of Strings +param foo: [], type: [String] # foo must be an array of strings, and has a default value of the empty array. +``` + +#### Accessing param values + +Params are accessible in the Component class as instance methods. + +For example: + +```ruby +class Hello < HyperComponent + # an immutable parameter, with a default of type String + param visitor: "World", type: String + + render do + "Hello #{visitor}" + end +end +``` + +### Immutable params + +A core design concept taken from React is that data flows down to child Components via params and params \(called props in React\) are immutable. + +However for complex objects + +In Hyperstack, there are **two exceptions** to this rule: + +* An instance of a **Store** \(passed as a param\) is mutable and changes to the state of the Store will cause a re-render +* An instance of a **Model** \(discussed in the Isomorphic section of these docs\) will also case a re-render when changed + +In the example below, clicking on the button will cause the Component to re-render \(even though `book` is a `param`\) because `book` is a Model. If `book` were not a Model \(or Store\) then the Component would not re-render. + +```ruby +class Likes < HyperComponent + param :book # book is an instance of the Book model + + render(DIV) do + P { "#{book.likes.count} likes" } + BUTTON { "Like" }.on(:click) { book.likes += 1} + end +end +``` + +> Note: Non-scalar params \(objects\) which are mutable through their methods are not read only. Care should be taken here as changes made to these objects will **not** cause a re-render of the Component. Specifically, if you pass a non-scalar param into a Component, and modify the internal data of that param, Hyperstack will not be notified to re-render the Component \(as it does not know about the internal structure of your object\). To achieve a re-render in this circumstance you will need to ensure that the parts of your object which are mutable are declared as state in a higher-order parent Component so that data can flow down from the parent to the child as per the React pattern. + +### Param Validation + +As your app grows it's helpful to ensure that your components are used correctly. We do this by allowing you to specify the expected ruby class of your parameters. When an invalid value is provided for a param, a warning will be shown in the JavaScript console. Note that for performance reasons type checking is only done in development mode. Here is an example showing typical type specifications: + +```ruby +class ManyParams < HyperComponent + param :an_array, type: [] # or type: Array + param :a_string, type: String + param :array_of_strings, type: [String] + param :a_hash, type: Hash + param :some_class, type: SomeClass # works with any class + param :a_string_or_nil, type: String, allow_nil: true +end +``` + +Note that if the param can be nil, add `allow_nil: true` to the specification. + +### Default Param Values + +React lets you define default values for your `params`: + +```ruby +class ManyParams < HyperComponent + param :an_optional_param, default: "hello", type: String, allow_nil: true +``` + +If no value is provided for `:an_optional_param` it will be given the value `"hello"` + +### Params of type Proc + +A Ruby `Proc` can be passed to a component like any other object. + +```ruby +param :all_done, type: Proc +... + # typically in an event handler +all_done(data).call +``` + +Proc params can be optional, using the `default: nil` and `allow_nil: true` options. Invoking a nil proc param will do nothing. This is handy for allowing optional callbacks. + +```ruby +class Alarm < HyperComponent + param :at, type: Time + param :notify, type: Proc + + after_mount do + @clock = every(1) do + if Time.now > at + notify.call + @clock.stop + end + force_update! + end + end + + render do + "#{Time.now}" + end +end +``` + +If for whatever reason you need to get the actual proc instead of calling it use `params.method(*symbol name of method*)` + +### Components as Params + +You can pass a Component as a `param` and then render it in the receiving Component. To create a Component without rendering it you use `.as_node`. This technique is used extensively in JavaScript libraries. + +```ruby +# in the parent Component... +button = MyButton().as_node +ButtonBar(button: button) + +class ButtonBar < HyperComponent + param :button + + render do + button.render + end +end +``` + +`as_node` can be attached to a component or tag, and removes the element from the rendering buffer and returns it. This is useful when you need store an element in some data structure, or passing to a native JS component. When passing an element to another Hyperstack Component `.as_node` will be automatically applied so you normally don't need it. + +`render` can be applied to the objects returned by `as_node` and `children` to actually render the node. + +```ruby +class Test < HyperComponent + param :node + + render do + DIV do + children.each do |child| + node.render + child.render + end + node.render + end + end +end +``` + +### Other Params + +A common type of React component is one that extends a basic HTML element in a simple way. Often you'll want to copy any HTML attributes passed to your component to the underlying HTML element. + +To do this use the `collect_other_params_as` method which will gather all the params you did not declare into a hash. Then you can pass this hash on to the child component + +```ruby +class CheckLink < HyperComponent + collect_other_params_as :attributes + render do + # we just pass along any incoming attributes + a(attributes) { '√ '.span; children.each &:render } + end +end +# CheckLink(href: "/checked.html") +``` + +Note: `collect_other_params_as` builds a hash, so you can merge other data in or even delete elements out as needed. diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index f37fb320f..9b0c6eb34 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -1,7 +1,5 @@ # HTML and CSS DSL -## HTML DSL - ### HTML elements A Hyperstack user-interface is composed of HTML elements, conditional logic and Components. @@ -11,9 +9,6 @@ UL do 5.times { |n| LI { "Number #{n}" }} end ``` - -> **Notice that the HTML elements \(BUTTON, DIV, etc.\) are in CAPS**. We know this is bending the standard Ruby style rules, but we think it reads better this way. - For example ```ruby @@ -47,50 +42,16 @@ TABLE(class: 'ui celled table') do end ``` -The following HTML and SVG elements are available: - -
    -HTML Tags
    
    -A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO  
    -B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON  
    -CANVAS CAPTION CITE CODE COL COLGROUP  
    -DATA DATALIST DD DEL DETAILS DFN DIALOG DIV DL DT  
    -EM EMBED  
    -FIELDSET FIGCAPTION FIGURE FOOTER FORM  
    -H1 H2 H3 H4 H5 H6 HEAD HEADER HR HTML  
    -I IFRAME IMG INPUT INS  
    -KBD KEYGEN  
    -LABEL LEGEND LI LINK  
    -MAIN MAP MARK MENU MENUITEM META METER  
    -NAV NOSCRIPT  
    -OBJECT OL OPTGROUP OPTION OUTPUT  
    -P PARAM PICTURE PRE PROGRESS  
    -Q  
    -RP RT RUBY  
    -S SAMP SCRIPT SECTION SELECT SMALL SOURCE SPAN STRONG STYLE SUB SUMMARY SUP  
    -TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK  
    -U UL  
    -VAR VIDEO  
    -WBR
    -
    -
    - - -
    -SVG Tags
    
    -CIRCLE CLIPPATH  
    -DEFS  
    -ELLIPSE  
    -G  
    -LINE LINEARGRADIENT  
    -MASK  
    -PATH PATTERN POLYGON POLYLINE  
    -RADIALGRADIENT RECT  
    -STOP  
    -SVG  
    -TEXT TSPAN
    -
    -
    +**[See the predefined tags summary for the complete list of HTML and SVG elements.](predefined-tags.md)** + +### Naming Conventions + +To distinguish between HTML and SVG tags, builtin components and Application Defined components, the following +naming conventions are followed: + ++ `ALLCAPS` denotes a HTML, SVG or builtin React psuedo component such as `FRAGMENT`. ++ `CamelCase` denotes an application defined component class like `TodoList`. + ### HTML parameters @@ -119,7 +80,7 @@ P(class: [:bright, :blue]) { } # class='bright blue' For `style` you need to pass a hash using the [React style conventions](https://reactjs.org/docs/dom-elements.html#style): ```ruby -PARA(style: { display: item[:some_property] == "some state" ? :block : :none }) +P(style: { display: item[:some_property] == "some state" ? :block : :none }) ``` ### Complex Arguments @@ -128,7 +89,7 @@ You can pass multiple hashes which will be merged, and any individual symbols (or strings) will be treated as `=true`. For example ```ruby -A(:flag, {href: '/'}, {class: 'my_class'}) +A(:flag, {href: '/'}, class: 'my_class') ``` will generate @@ -136,3 +97,5 @@ will generate ```HTML ``` + +> **[more on passing hashes to methods](notes.html#ruby-hash-params)** diff --git a/docs/client-dsl/notes.md b/docs/client-dsl/notes.md new file mode 100644 index 000000000..2b5d54133 --- /dev/null +++ b/docs/client-dsl/notes.md @@ -0,0 +1,174 @@ +## Notes + +### Blocks in Ruby + +Ruby methods may *receive* a block which is simply an anonymous function. + +The following code in Ruby + +```ruby +some_method(1, 2, 3) { |x| puts x } +``` +is roughly equivilent to this Javascript +```JavaScript +some_method(1, 2, 3 function(x) { console.log(x) }) +``` +In Ruby blocks may be specified either using `do ... end` or with `{ ... }` + +```Ruby +some_method { an_expression } +# or +some_method do + several + expressions +end +``` +Standard style reserves the `{ ... }` notation for single line blocks, and `do ... end` for multiple line blocks + +### Ruby Hash Params + +In Ruby if the final argument to a method is a hash you may leave the `{...}` off: + +```RUBY +some_method(1, 2, {a: 2, b: 3}) # same as +some_method(1, 2, a: 2, b: 3) +``` + +### The HyperComponent Base Class + +By convention all your components inherit from the `HyperComponent` base class, which would typically look like this: + +```ruby +# components/hyper_component.rb +class HyperComponent + # All component classes must include Hyperstack::Component + include Hyperstack::Component + # The Observable module adds state handling + include Hyperstack::State::Observable + # The following turns on the new style param accessor + # i.e. param :foo is accessed by the foo method + param_accessor_style :accessors +end +``` +> The Hyperstack Rails installer and generators will create this class for you if it does not exist, or you may copy the +above to your `components` directory. + +Having an application wide `HyperComponent` class allows you to modify component behavior on an application basis, similar to the way Rails uses `ApplicationRecord` and `ApplicationController` classes. + +> This is just a convention. Any class that includes the `Hyperstack::Component` module can be used as a Component. You also do not have +to name it `HyperComponent`. For example some teams prefer `ApplicationComponent` more closely following the +Rails convention. If you use a different name for this class be sure to set the `Hyperstack.component_base_class` setting so the +Rails generators will use the proper name when generating your components. **[more details...](/rails-installation/generators.html#specifying-the-base-class)** + +### Abstract and Concrete Components + +An *abstract* component class is intended to be the base class of other components, and thus does not have a render block. +A class that defines a render block is a concrete class. The +distinction between *abstract* and *concrete* is useful to distinguish classes like `HyperComponent` that are intended +to be subclassed. + +Abstract classes are often used to share common code between subclasses. + +### Word Count Method + +```RUBY +def word_count(text) + text.downcase # all lower case + .gsub(/\W/, ' ') # get rid of special chars + .split(' ') # divide into an array of words + .group_by(&:itself) # group into arrays of the same words + .map{|k, v| [k, v.length]} # convert to [word, # of words] + .sort { |a, b| b[1] <=> a[1] } # sort descending (that was fun!) +end +``` + +### Ownership + +In the Avatar example instances of `Avatar` _own_ instances of `ProfilePic` and `ProfileLink`. In Hyperstack (like React), **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is _owned by_ `Y`. As will be discussed later a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent. + +It's important to draw a distinction between the owner-owned-by relationship and the parent-child relationship. The owner-owned-by relationship is specific to Hyperstack/React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `DIV`, `ProfilePic` and `ProfileLink` instances, and `DIV` is the **parent** \(but not owner\) of the `ProfilePic` and `ProfileLink` instances. + +```ruby +class Avatar < HyperComponent + param :user_name + + render do # this can be shortened to render(DIV) do - see the previous section + DIV do + ProfilePic(user_name: user_name) # belongs to Avatar, owned by DIV + ProfileLink(user_name: user_name) # belongs to Avatar, owned by DIV + end + end +end + +class ProfilePic < HyperComponent + param :user_name + render { IMG(src: "https://graph.facebook.com/#{user_name}/picture") } +end + +class ProfileLink < HyperComponent + param :user_name + render do + A(href: "https://www.facebook.com/#{user_name}") do + user_name + end + end +end +``` + +### Generating Keys + +Every Hyperstack object whether its a string, integer, or some complex class responds to the `to_key` method. +When you provide a component's key parameter with any object, the object's to_key method will be called, and +return a unique key appropriate to that object. + +For example strings, and numbers return themselves. Other complex objects return the internal `object_id`, and +some classes provide their own `to_key` method that returns some invariant value for that class. HyperModel records +return the database id for example. + +If you are creating your own data classes keep this in mind. You simply define a `to_key` method on the class +that returns some value that will be unique to that instance. And don't worry if you don't define a method, it will +default to the one provided by Hyperstack. + +### Proper Use Of Keys + +> For best results the `key` is supplied at highest level possible. (NOTE THIS MAY NO LONGER BE AN ISSUE IN LATEST REACT) +```ruby +# WRONG! +class ListItemWrapper < HyperComponent + param :data + render do + LI(key: data[:id]) { data[:text] } + end +end + +class MyComponent < HyperComponent + param :results + render do + UL do + result.each do |result| + ListItemWrapper data: result + end + end + end +end +``` +```ruby +# CORRECT +class ListItemWrapper < HyperComponent + param :data + render do + LI { data[:text] } + end +end + +class MyComponent < HyperComponent + param :results + render do + UL do + results.each do |result| + ListItemWrapper key: result[:id], data: result + end + end + end +end +``` diff --git a/docs/client-dsl/predefined-tags.md b/docs/client-dsl/predefined-tags.md new file mode 100644 index 000000000..7f1d661b1 --- /dev/null +++ b/docs/client-dsl/predefined-tags.md @@ -0,0 +1,41 @@ +#### HTML Tags +``` +A ABBR ADDRESS AREA ARTICLE ASIDE AUDIO +B BASE BDI BDO BIG BLOCKQUOTE BODY BR BUTTON +CANVAS CAPTION CITE CODE COL COLGROUP +DATA DATALIST DD DEL DETAILS DFN DIALOG DIV DL DT +EM EMBED +FIELDSET FIGCAPTION FIGURE FOOTER FORM +H1 H2 H3 H4 H5 H6 HEAD HEADER HR HTML +I IFRAME IMG INPUT INS +KBD KEYGEN +LABEL LEGEND LI LINK +MAIN MAP MARK MENU MENUITEM META METER +NAV NOSCRIPT +OBJECT OL OPTGROUP OPTION OUTPUT +P PARAM PICTURE PRE PROGRESS +Q +RP RT RUBY +S SAMP SCRIPT SECTION SELECT SMALL SOURCE SPAN STRONG STYLE SUB SUMMARY SUP +TABLE TBODY TD TEXTAREA TFOOT TH THEAD TIME TITLE TR TRACK +U UL +VAR VIDEO +WBR +``` +#### SVG Tags +``` +CIRCLE CLIPPATH +DEFS +ELLIPSE +G +LINE LINEARGRADIENT +MASK +PATH PATTERN POLYGON POLYLINE +RADIALGRADIENT RECT +STOP +SVG +TEXT TSPAN +``` +#### Other Builtin Tags + +The `FRAGMENT` is used to return multiple *static* children from a component or block. The only valid param to `FRAGMENT` is `key`. See the **[React documentation](https://reactjs.org/docs/fragments.html)** for more details. diff --git a/docs/rails-installation/README.md b/docs/rails-installation/README.md index 1a1fb6e63..1866f8678 100644 --- a/docs/rails-installation/README.md +++ b/docs/rails-installation/README.md @@ -6,23 +6,4 @@ Adding Hyperstack to your existing Rails App is as simple as adding the gem and Continue to the next section to make sure you have the necessary prerequisites on your machine. -
    -Why Rails?
    -

    Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality.

    People sometimes balk at Rails because when they see the huge number of files and directories generated by the Rails installer, it looks crazy, complex, and ineffecient. Keep in mind that this has very little if any impact on your applications performance, and when developing code 90% of your time will be spent in the following directories: app/models and app/hyperstack. The rest of the files are there to hold configuration files, and seldom used content, so they have a place out of the way of your main development activities.

    Developers often believe that Rails modules like ActionController and ActiveRecord while powerful are slow. In the case of Hyperstack this is largely irrelevant since one of our goals is to offload as much work to the client as possible. For example rather than have a multitude of controllers delivering different page views and updates, your client side Hyperstack code is now responsible for that. The role of the server becomes the central database, and the place where secure operations are executed (such as sending mail, authenticating users etc.)

    How about other Rack Frameworks

    But still you may have specific needs for a lighter weight system, or have an existing Sinatra app (for example) that you would like to use with Hyperstack. For now we will say its in the plan, and its just a matter of time. If you are interested leave a comment on this issue: https://github.com/hyperstack-org/hyperstack/issues/340

    -
    - - -{% fold summary="Why Rails?" %} - ->Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality. -> ->People sometimes balk at Rails because when they see the huge number of files and directories generated by the Rails installer, it looks crazy, complex, and ineffecient. Keep in mind that this has very little if any impact on your applications performance, and when developing code 90% of your time will be spent in the following directories: `app/models` and `app/hyperstack`. The rest of the files are there to hold configuration files, and seldom used content, so they have a place out of the way of your main development activities. -> ->Developers often believe that Rails modules like ActionController and ActiveRecord while powerful are slow. -In the case of Hyperstack this is largely irrelevant since one of our goals is to offload as much work to the client as possible. For example rather than have a multitude of controllers delivering different page views and updates, your client side Hyperstack code is now responsible for that. The role of the server becomes the central database, and the place where secure operations are executed (such as sending mail, authenticating users etc.) -> ->### How about other Rack Frameworks -> ->But still you may have specific needs for a lighter weight system, or have an existing Sinatra app (for example) that you would like to use with Hyperstack. For now we will say its in the plan, and its just a matter of time. If you are interested leave a comment on this issue: https://github.com/hyperstack-org/hyperstack/issues/340 - -{% endfold %} +**[For more info on why we use Rails](why-rails.md)** diff --git a/docs/rails-installation/file-structure.md b/docs/rails-installation/file-structure.md index 5a2cfc528..29a2e6217 100644 --- a/docs/rails-installation/file-structure.md +++ b/docs/rails-installation/file-structure.md @@ -18,10 +18,10 @@ In addition there are configuration settings in existing Rails files that are ex Here lives all your Hyperstack code that will run on the client. Some of the subdirectories are *isomorphic* meaning the code is shared between the client and the server, other directories are client only. -Within the `hyperstack` directory there will be the following sub-directories: +Within the `hyperstack` directory there can be the following sub-directories: + `components` *(client-only)* is where your components live. - Following Rails conventions a component with a class of `Bar::None::FooManchu` should be in a file named `components/bar/none/foo_manchu.rb` +> Following Rails conventions a component with a class of `Bar::None::FooManchu` should be in a file named `components/bar/none/foo_manchu.rb` + `models` *(isomorphic)* is where ActiveRecord models are shared with the client. More on this below. @@ -33,9 +33,17 @@ Within the `hyperstack` directory there will be the following sub-directories: ### Sharing Models and Operations -Files in the `hyperstack` `/models` and `/operations` directories are loaded on the client and the server. So when you place a model's class definition in the `hyperstack/models` directory the class is available on the client. +Files in the `hyperstack` `/models` and `/operations` directories are loaded on the client *and* the server. So when you place a model's class definition in the `hyperstack/models` directory the class is available on the client. +Assuming: ```Ruby +# app/hyperstack/models/todo.rb +class Todo < ApplicationRecord + ... +end +``` +Then +``` Todo.count # will return the same value on the client and the server ``` @@ -53,13 +61,13 @@ This works because Ruby classes are *open*, so that you can define a class (or m ### Server Side Operations -Operations are Hyperstack's way of providing *Service Objects*: classes that perform some operation not strictly belonging to a single model, and often involving other services such as remote APIs. +Operations are Hyperstack's way of providing *Service Objects*: classes that perform some operation not strictly belonging to a single model, and often involving other services such as remote APIs. *The idea of Operations comes from the [Trailblazer Framework.](https://trailblazer.to/2.0/gems/operation/2.0/index.html)* As such Operations can be useful strictly on the server side, and so can be added to the `app/operations` directory. Server side operations can also be remotely run from the client. Such operations are defined as subclasses of `Hyperstack::ServerOp`. -The right way to define a `ServerOp` is to place its basic definition including its parameter signature in the `hyperstack/operations` directory, and then placing the rest of the operations definition in the `app/operations` directory. +The right way to define a `ServerOp` is to place its basic definition including its parameter signature in the `hyperstack/operations` directory, and then placing the rest of the operation's definition in the `app/operations` directory. ### Policies diff --git a/docs/rails-installation/generators.md b/docs/rails-installation/generators.md index 776091b0b..04f469d2a 100644 --- a/docs/rails-installation/generators.md +++ b/docs/rails-installation/generators.md @@ -26,7 +26,7 @@ bundle exec rails g hyper:component ComponentName #### File directories and Name Spacing Components -The above will create a new class definition for `ComponentName` in a file named `component_name.rb` and place it in +The above will create a new class definition for `MyComponent` in a file named `my_component.rb` and place it in the `app/hyperstack/components/` directory. The component may be name spaced and will be placed in the appropriate subdirectory. I.e. `Foo::BarSki` will generate `app/hyperstack/components/foo/bar_ski.rb` @@ -40,7 +40,7 @@ component with the `--no-help` flag. ### Router Generator Typically your top level component will be a *Router* which will take care of dispatching to specific components as the URL changes. This provides the essence of a *Single Page App* where as the user moves between parts of -the application the URL is updated, the back and forward buttons work, but the page is **not** reloaded from the server. +the application the URL is updated, the *back* and *forward* buttons work, but the page is **not** reloaded from the server. A component becomes a router by including the `Hyperstack::Router` module which provides a number of methods that will be used in the router @@ -52,46 +52,30 @@ To generate a new router skeleton use the `hyper:router` generator: bundle exec rails g hyper:router App ``` -Note that we now have two routers to deal with. The server still has the `routes.rb` file that maps incoming requests to a Rails controller which -will provide the appropriate response. +> Note that in any Single Page App there will be two routers in play. +On the server the router is responsible dispatching each incoming HTTP request to a +controller. The controller will deliver back (usually using a view) the contents of the request. +> +> In addition on a Single Page App you will have a router running on the client, which will dispatch to different components depending on the current value of the URL. The server is only contacted if the current URL leaves the set of URLs that client router knows how to deal with. -On the client the router there maps the current url to a component. - -#### Routing to Your Components from Rails - -Components can be directly mounted from the Rails `routes.rb` file, using the builtin Hyperstack controller. - -For example a Rails `routes.rb` file containing - -```ruby - get 'some_page/(*others)', to: 'hyperstack#some_component' -``` - -will route all urls beginning with `some_page` to `SomeComponent`. +#### Adding a Route to the Rails `routes.rb` File When you generate a new component you can use the `--add-route` option to add the route for you. For example: ``` -bundle exec rails g hyper:router SomeComponent \ - --add-route="some_page/(*others)" +bundle exec rails g hyper:router MainApp \ + --add-route="/(*others)" ``` - -would add the route shown above. - -Note that typically the Rails route will be going to a Hyperstack Router Component. That is why we add the wild card to the Rails route so that all urls beginning with `some_page/` will all be handled by `SomeComponent`. - -Also note that for the purposes of the example we are using rather dubious names, a more logical setup would be: +will add ```ruby - get `/(*others)`, to 'hyperstack#app' + get '/(*others)', to: 'hyperstack#main_app' ``` +to the Rails `routes.rb` file, which will direct all URLS to the `MainApp` component. -Which you could generate with -``` -bundle exec rails g hyper:router App --add-route="/(*others)" -``` +For details see **[Routing and Mounting Components.](routing-and-mounting-components.md)** -#### Changing the Base Class +#### Specifying the Base Class By default components will inherit from the `HyperComponent` base class. diff --git a/docs/rails-installation/other-details.md b/docs/rails-installation/other-details.md index a647d55a6..4b662fdf1 100644 --- a/docs/rails-installation/other-details.md +++ b/docs/rails-installation/other-details.md @@ -1,6 +1,6 @@ ## Other Rails Configuration Details -Hyperstack sets a number of Rails configurations as outlined below. +Hyperstack internally sets a number of Rails configurations as outlined below. >These are all setup automatically by the hyperstack generators and installers. They are documented here for advanced configuration or in the sad chance that something gets broken during your setup. Please report any issues with setup, or if you feel you have to manually tweak things. @@ -91,15 +91,15 @@ Otherwise the following settings are automatically applied in test and staging: # This will prevent any data transmitted by HyperOperation from appearing in logs config.filter_parameters << :hyperstack_secured_json -# Add the hyperstack directories -config.eager_load_paths += %W(#{config.root}/app/hyperstack/models) -config.eager_load_paths += %W(#{config.root}/app/hyperstack/models/concerns) -config.eager_load_paths += %W(#{config.root}/app/hyperstack/operations) -config.eager_load_paths += %W(#{config.root}/app/hyperstack/shared) + # Add the hyperstack directories + config.eager_load_paths += %W(#{config.root}/app/hyperstack/models) + config.eager_load_paths += %W(#{config.root}/app/hyperstack/models/concerns) + config.eager_load_paths += %W(#{config.root}/app/hyperstack/operations) + config.eager_load_paths += %W(#{config.root}/app/hyperstack/shared) -# But remove the outer hyperstack directory so rails doesn't try to load its -# contents directly -delete_first config.eager_load_paths, "#{config.root}/app/hyperstack" + # But remove the outer hyperstack directory so rails doesn't try to load its + # contents directly + delete_first config.eager_load_paths, "#{config.root}/app/hyperstack" ``` but in production we autoload instead of eager load. ```ruby @@ -111,5 +111,4 @@ but in production we autoload instead of eager load. # except for the outer hyperstack directory delete_first config.autoload_paths, "#{config.root}/app/hyperstack" -end ``` diff --git a/docs/rails-installation/prerequisites.md b/docs/rails-installation/prerequisites.md index 61443e8e6..dcc5cbe92 100644 --- a/docs/rails-installation/prerequisites.md +++ b/docs/rails-installation/prerequisites.md @@ -2,7 +2,8 @@ #### Rails -Hyperstack is currently tested on Rails ~> 5.0 and ~> 6.0, if you are on Rails 4.0 it might be time to upgrade, but that said you probably can manually install Hyperstack on Rails 4.0 and get it working. +Hyperstack is currently tested on Rails ~> 5.0 and ~> 6.0. +>If you are on Rails 4.0 it might be time to upgrade, but that said you probably can manually install Hyperstack on Rails 4.0 and get it working. [Rails Install Instructions](http://railsinstaller.org/en) @@ -13,21 +14,21 @@ need yarn. To skip adding webpacker use `hyperstack:install:skip-webpack` when [Yarn Install Instructions](https://yarnpkg.com/en/docs/install#mac-stable) -#### - Database +#### Database To fully utilize Hyperstack's capabilities you will be need an SQL database that has an ActiveRecord adapter. If you have a choice we have found Postgresql works best (and it also deploys to Heroku without issue.) If you are new to Rails, then the default Sqlite database (which rails will install) will work fine. -> Why Don't You Support NoSql databases? The biggest reasons are security and effeciency. Hyperstack access policies are based on known table names and attributes, and after commit hooks. Keep in mind that modern DBs (and Hyperstack support the json and jsonb attribute types allowing you to add arbitrary json based data to your database) +> Why don't we support NoSql databases? The biggest reasons are security and effeciency. Hyperstack access-policies are based on known table names and attributes and after-commit hooks. Keep in mind that modern DBs support the json and jsonb attribute types allowing you to add arbitrary json based data to your database. ### Creating a New Rails App -If you don't have an existing Rails app to add Hyperstack to, you can create a new Rails app +If you don't have an existing Rails app you can create a new Rails app with the following command line: ``` bundle exec rails new NameOfYourApp -T ``` -To avoid much pain, do not name your app `Application` as this will conflict with all sorts of +To avoid much pain do not name your app `Application` as this will conflict with all sorts of things in Rails and Hyperstack. Once you have created the app cd into the newly created directory. diff --git a/docs/rails-installation/routing-and-mounting-components.md b/docs/rails-installation/routing-and-mounting-components.md new file mode 100644 index 000000000..fc8969420 --- /dev/null +++ b/docs/rails-installation/routing-and-mounting-components.md @@ -0,0 +1,86 @@ +## Routing and Mounting Components + +Within a Rails Application there are three ways to render or *mount* a +component on a page: + ++ Route directly to the component from the rails `routes.rb` file. ++ Render a component directly from a controller. ++ Render a component from within a layout or view file. + +### Routing Directly to Components + +Components can be directly mounted from the Rails `routes.rb` file, using the builtin Hyperstack controller. + +For example a Rails `routes.rb` file containing + +```ruby + get 'some_page/(*others)', to: 'hyperstack#some_component' +``` + +will route all urls beginning with `some_page` to `SomeComponent`. + +When you generate a new component you can use the `--add-route` option to add the route for you (see the previous section.) + +Note that typically the Rails route will be going to a Router Component. That is why we typically add the wild card to the Rails route so that all urls beginning with `some_page/` will all be handled by `SomeComponent` without having to reload the page. + +Also note that for the purposes of the example we used rather dubious names, a more logical setup would be: + +```ruby + get `/(*others)`, to 'hyperstack#app' +``` + +Which you could generate with +``` +bundle exec rails g hyper:router App --add-route="/(*others)" +``` + +You could also divide your application into several single page apps, for example + +```ruby +... + get 'admin/(*others)', to: 'hyperstack#admin' + get '/(*others)', to: 'hyperstack#app' +... +``` + +would route all URLS beginning with admin to the `Admin` component, and everything else +to the main `App` component. *Note that order of the routes is important as Rails will +dispatch to the first route it matches.* + +> If the component is named spaced separate each module with a double underscore (`__`) and +leave the module names CamelCase: +```ruby + get 'admin/(*others)', to: 'hyperstack#Admin__App' +``` + +### Rendering a Component from a Controller + +To render a component from a controller use the `render_component` helper: + +```ruby + render_component 'Admin', {user_id: params[:id]}, layout: 'admin' + # would pass the user_id to the Admin component and use the admin layout + + # in general: + render_component 'The::Component::Name' + { ... component params ... }, + { other render params such as layout } +``` + +Only the component name is required, but note that if you want to have other +render params, you will have to supply at least an empty hash for the component +params. + +### Rendering (or Mounting) a Component from a View + +To mount a component directly in a view use the mount_component view helper: + +```html + <%= mount_component 'Clock' %> +``` + +Like render_component may take params which will be passed to the mounted component. + +Mounting a component in an existing view, is a very useful way to integrate Hyperstack +into existing applications. You mount a component to serve a specific function such as +a dynamic footer or a tweeter feed onto an existing view without having to do a major redesign. diff --git a/docs/rails-installation/using-the-installer.md b/docs/rails-installation/using-the-installer.md index 430a8dce0..ce71194fb 100644 --- a/docs/rails-installation/using-the-installer.md +++ b/docs/rails-installation/using-the-installer.md @@ -1,6 +1,8 @@ ## Installing HyperStack -* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your gem file +In the directory of your existing or newly created Rails app: + +* add `gem 'rails-hyperstack', "~> 1.0.alpha1.0"` to your `Gemfile` * run `bundle install` * run `bundle exec rails hyperstack:install` @@ -28,6 +30,8 @@ You should see the page on the browser change to "Hello World" You are in business! +> If this does not work, please contact us on *[slack](https://hyperstack.org/slack)*, or **[create an issue on github.](https://github.com/hyperstack-org/hyperstack/issues/new)** + ### Installer Options You can control what gets installed with the following options: diff --git a/docs/rails-installation/why-rails.md b/docs/rails-installation/why-rails.md new file mode 100644 index 000000000..6d3f11135 --- /dev/null +++ b/docs/rails-installation/why-rails.md @@ -0,0 +1,11 @@ +### Why Rails? +Rails provides a robust, tried and true tool chain that takes care of much of the day to day details of building your app. Hyperstack builds on the Rails philosophy of convention over configuration, meaning that there is almost no boiler plate code in your Rails-Hyperstack application. Almost every line that you write for your Hyperstack application will deal with the application requirements. We have seen real reductions of up to 400% in the lines of code needed to deliver high quality functionality. + +People sometimes balk at Rails because when they see the huge number of files and directories generated by the Rails installer, it looks crazy, complex, and ineffecient. Keep in mind that this has very little if any impact on your application's performance, and when developing code 90% of your time will be spent in the following directories: `app/models` and `app/hyperstack`. The rest of the files are there to hold configuration files, and seldom used content, so they have a place out of the way of your main development activities. + +Developers often believe that Rails modules like ActionController and ActiveRecord while powerful are slow. +In the case of Hyperstack this is largely irrelevant since one of our goals is to offload as much work to the client as possible. For example rather than have a multitude of controllers delivering different page views and updates, your client side Hyperstack code is now responsible for that. The role of the server becomes the central database, and the place where secure operations are executed (such as sending mail, authenticating users etc.) + +### How about other Rack Frameworks? + +But still you may have specific needs for a lighter weight system, or have an existing Sinatra app (for example) that you would like to use with Hyperstack. For now we will say it's in the plan, and it's just a matter of time. If you are interested leave a comment on this issue: https://github.com/hyperstack-org/hyperstack/issues/340 diff --git a/ruby/rails-hyperstack/.gitignore b/ruby/rails-hyperstack/.gitignore index 71aa55cc3..24bb217df 100644 --- a/ruby/rails-hyperstack/.gitignore +++ b/ruby/rails-hyperstack/.gitignore @@ -11,6 +11,7 @@ capybara-*.html **.orig rerun.txt pickle-email-*.html +TestApp/ # TODO Comment out these rules if you are OK with secrets being uploaded to the repo config/initializers/secret_token.rb From 09de77ac67865a8c2129727ebe6bbd2394827046 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 19 Mar 2021 05:36:43 -0400 Subject: [PATCH 213/307] removed .html from notes links --- docs/client-dsl/component-basics.md | 6 +++--- docs/client-dsl/component-details.md | 6 +++--- docs/client-dsl/html-css.md | 10 ++++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/client-dsl/component-basics.md b/docs/client-dsl/component-basics.md index e5af946a6..52e27a47e 100644 --- a/docs/client-dsl/component-basics.md +++ b/docs/client-dsl/component-basics.md @@ -20,11 +20,11 @@ class MyComponent < HyperComponent ... end ``` -> **[More on the HyperComponent base class](notes.html#the-hypercomponent-base-class)** +> **[More on the HyperComponent base class](notes#the-hypercomponent-base-class)** ## The `render` Callback -At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components](notes.html#abstract-and-concrete-components)** +At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components](notes#abstract-and-concrete-components)** ```ruby class Component < HyperComponent @@ -33,7 +33,7 @@ class Component < HyperComponent end end ``` -> The code between the `do` and `end` is called a block. **[More here...](notes.html#blocks-in-ruby)** +> The code between the `do` and `end` is called a block. **[More here...](notes#blocks-in-ruby)** To save a little typing you can also specify the top level element to be rendered: diff --git a/docs/client-dsl/component-details.md b/docs/client-dsl/component-details.md index c4d1ba04e..62344e603 100644 --- a/docs/client-dsl/component-details.md +++ b/docs/client-dsl/component-details.md @@ -35,9 +35,9 @@ end ``` Here we don't determine the actual number or contents of the `LI` children until runtime. ->**[The `word_count` method...](notes.html#word-count-method)** +>**[The `word_count` method...](notes#word-count-method)** ->Dynamically generating components creates a new concept called ownership. **[More here...](notes.html#ownership)** +>Dynamically generating components creates a new concept called ownership. **[More here...](notes#ownership)** ### Keys @@ -62,7 +62,7 @@ identify the ones that are the same: You don't have to stress out too much about keys, its easy to add them later. Just keep the concept in mind when you are generating long lists, tables, and divs with many children. -> **[More on how Hyperstack generates keys...](notes.html#generating-keys)** +> **[More on how Hyperstack generates keys...](notes#generating-keys)** diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index 9b0c6eb34..e98c703e2 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -46,14 +46,12 @@ end ### Naming Conventions -To distinguish between HTML and SVG tags, builtin components and Application Defined components, the following +To distinguish between HTML and SVG tags, builtin components and application defined components the following naming conventions are followed: -+ `ALLCAPS` denotes a HTML, SVG or builtin React psuedo component such as `FRAGMENT`. ++ `ALLCAPS` denotes a HTML, SVG or builtin React psuedo components such as `FRAGMENT`. + `CamelCase` denotes an application defined component class like `TodoList`. - - ### HTML parameters You can pass any expected parameter to a HTML or SVG element: @@ -77,7 +75,7 @@ P(class: :bright) { } P(class: [:bright, :blue]) { } # class='bright blue' ``` -For `style` you need to pass a hash using the [React style conventions](https://reactjs.org/docs/dom-elements.html#style): +For `style` you need to pass a hash using the **[React style conventions](https://reactjs.org/docs/dom-elements.html#style):** ```ruby P(style: { display: item[:some_property] == "some state" ? :block : :none }) @@ -98,4 +96,4 @@ will generate ``` -> **[more on passing hashes to methods](notes.html#ruby-hash-params)** +> **[more on passing hashes to methods](notes#ruby-hash-params)** From 367ffeaf2d80d61f360839fc6fad1b74f4f45489 Mon Sep 17 00:00:00 2001 From: catmando Date: Fri, 19 Mar 2021 05:39:19 -0400 Subject: [PATCH 214/307] removed .html from notes links changed to .md --- docs/client-dsl/component-basics.md | 6 +++--- docs/client-dsl/component-details.md | 6 +++--- docs/client-dsl/html-css.md | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/client-dsl/component-basics.md b/docs/client-dsl/component-basics.md index 52e27a47e..9c69b58c0 100644 --- a/docs/client-dsl/component-basics.md +++ b/docs/client-dsl/component-basics.md @@ -20,11 +20,11 @@ class MyComponent < HyperComponent ... end ``` -> **[More on the HyperComponent base class](notes#the-hypercomponent-base-class)** +> **[More on the HyperComponent base class](notes.md#the-hypercomponent-base-class)** ## The `render` Callback -At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components](notes#abstract-and-concrete-components)** +At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components](notes.md#abstract-and-concrete-components)** ```ruby class Component < HyperComponent @@ -33,7 +33,7 @@ class Component < HyperComponent end end ``` -> The code between the `do` and `end` is called a block. **[More here...](notes#blocks-in-ruby)** +> The code between the `do` and `end` is called a block. **[More here...](notes.md#blocks-in-ruby)** To save a little typing you can also specify the top level element to be rendered: diff --git a/docs/client-dsl/component-details.md b/docs/client-dsl/component-details.md index 62344e603..73b387a01 100644 --- a/docs/client-dsl/component-details.md +++ b/docs/client-dsl/component-details.md @@ -35,9 +35,9 @@ end ``` Here we don't determine the actual number or contents of the `LI` children until runtime. ->**[The `word_count` method...](notes#word-count-method)** +>**[The `word_count` method...](notes.md#word-count-method)** ->Dynamically generating components creates a new concept called ownership. **[More here...](notes#ownership)** +>Dynamically generating components creates a new concept called ownership. **[More here...](notes.md#ownership)** ### Keys @@ -62,7 +62,7 @@ identify the ones that are the same: You don't have to stress out too much about keys, its easy to add them later. Just keep the concept in mind when you are generating long lists, tables, and divs with many children. -> **[More on how Hyperstack generates keys...](notes#generating-keys)** +> **[More on how Hyperstack generates keys...](notes.md#generating-keys)** diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index e98c703e2..0d5adfa36 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -96,4 +96,4 @@ will generate ``` -> **[more on passing hashes to methods](notes#ruby-hash-params)** +> **[more on passing hashes to methods](notes.md#ruby-hash-params)** From 9e2f3bdd19090b4f64ee4a28ec61f9cf3b0f3e22 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 25 Mar 2021 17:05:12 -0400 Subject: [PATCH 215/307] bunch of doc updates --- docs/SUMMARY.md | 28 +- docs/client-dsl/README.md | 31 +- docs/client-dsl/component-basics.md | 88 ++++- docs/client-dsl/component-details.md | 260 +------------ docs/client-dsl/elements-and-rendering.md | 3 +- docs/client-dsl/event-handlers.md | 298 --------------- docs/client-dsl/html-css.md | 3 +- docs/client-dsl/hyper-router.md | 430 ---------------------- docs/client-dsl/hyper-store.md | 123 ------- docs/client-dsl/javascript-components.md | 27 +- docs/client-dsl/lifecycle-methods.md | 65 ++-- docs/client-dsl/notes.md | 68 +++- docs/client-dsl/state.md | 260 +++++-------- 13 files changed, 300 insertions(+), 1384 deletions(-) delete mode 100644 docs/client-dsl/event-handlers.md delete mode 100644 docs/client-dsl/hyper-router.md delete mode 100644 docs/client-dsl/hyper-store.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 45205f0fd..1a936b376 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -9,27 +9,31 @@ * [Routing and Mounting Components](rails-installation/routing-and-mounting-components.md) * [Other Rails Configuration Details](rails-installation/other-details.md) * [Why Rails? Other Frameworks?](rails-installation/why-rails.md) -* [Client DSL](client-dsl/README.md) - * [HTML & CSS DSL](client-dsl/html-css.md) - * [Component DSL](client-dsl/component-basics.md) +* [HyperComponent](client-dsl/README.md) + * [Component Classes](client-dsl/component-basics.md) + * [HTML Tags & CSS Classes](client-dsl/html-css.md) * [Component Children, Keys and Fragments](client-dsl/component-details.md) + * [Component Params](client-dsl/params.md) * [Lifecycle Methods](client-dsl/lifecycle-methods.md) - * [State](client-dsl/state.md) - * [Event Handlers](client-dsl/event-handlers.md) + * [Component State](client-dsl/state.md) + * [Events and Callbacks](client-dsl/events-and-callbacks.md) + * [Recovering from Errors](client-dsl/error-recovery.md) * [JavaScript Components](client-dsl/javascript-components.md) - * [Client-side Routing](client-dsl/hyper-router.md) - * [Stores](client-dsl/hyper-store.md) * [Elements and Rendering](client-dsl/elements-and-rendering.md) - * [Further Reading](client-dsl/further-reading.md) - * [List of Predefined Tags and Components](client-dsl/predefined-tags.md) + * [Summary of Methods](client-dsl/methods.md) + * [List of Predefined Tags & Components](client-dsl/predefined-tags.md) + * [Predefined Events](client-dsl/predefined-events.md) * [Notes](client-dsl/notes.md) -* [Isomorphic DSL](isomorphic-dsl/README.md) + * [Further Reading](client-dsl/further-reading.md) +* [HyperState](hyper-state/README.md) +* [HyperRouter](hyper-router/README.md) +* [HyperModel](isomorphic-dsl/README.md) * [Isomorphic Models](isomorphic-dsl/hyper-model.md) - * [Isomorphic Operations](isomorphic-dsl/hyper-operation.md) +* [Isomorphic Operations](isomorphic-dsl/hyper-operation.md) * [Policies](isomorphic-dsl/hyper-policy.md) +* [Internationalization](development-workflow/hyper-i18n.md) * [Development Workflow](development-workflow/README.md) * [Debugging](development-workflow/debugging.md) - * [Internationalization](development-workflow/hyper-i18n.md) * [HyperSpec](development-workflow/hyper-spec/README.md) * [Installation](development-workflow/hyper-spec/01-installation.md) * [Tutorial](development-workflow/hyper-spec/02-tutorial.md) diff --git a/docs/client-dsl/README.md b/docs/client-dsl/README.md index 1464343d5..22aaa021d 100644 --- a/docs/client-dsl/README.md +++ b/docs/client-dsl/README.md @@ -1,7 +1,3 @@ -# Client DSL - -## Hyperstack Component Classes and DSL - Your Hyperstack Application is built from a series of *Components* which are Ruby Classes that display portions of the UI. Hyperstack Components are implemented using [React](https://reactjs.org/), and can interoperate with existing React components and libraries. Here is a simple example that displays a ticking clock: ```ruby @@ -9,11 +5,16 @@ Your Hyperstack Application is built from a series of *Components* which are Rub # which supplies the DSL to translate from Ruby into React # function calls class Clock < HyperComponent - # before_mount is an example of a life cycle method. - before_mount do - # before the component is first rendered (mounted) + # Components can be parameterized. + # in this case you can override the default + # with a different format + param format: "%m/%d/%Y %I:%M:%S" + # After_mount is an example of a life cycle method. + after_mount do + # Before the component is first rendered (mounted) # we setup a periodic timer that will update the # current_time instance variable every second. + # The mutate method signals a change in state every(1.second) { mutate @current_time = Time.now } end # every component has a render block which describes what will be @@ -22,21 +23,9 @@ class Clock < HyperComponent # Components can render other components or primitive HTML or SVG # tags. Components also use their state to determine what to render, # in this case the @current_time instance variable - DIV { @current_time.strftime("%m/%d/%Y %I:%M:%S") + DIV { @current_time.strftime(format) } end end ``` -This documentation will cover the following core concepts, many of which -are touched on in the above simple example: - -+ [HTML & CSS DSL](html-css.md) which provides Ruby implementations of all of the HTML and CSS elements -+ [The Component DSL](components.md) is a Ruby DSL which wraps ReactJS Components -+ [Lifecycle Methods](lifecycle-methods.md) are methods which are invoked before, during and after rendering -+ [State](state.md) - components rerender as needed when state changes. -+ [Event Handlers](event-handlers.md) allow any HTML element or Component to respond to an event, plus custom events can be described. -+ [JavaScript Components](javascript-components.md) access to the full universe of JS libraries in your Ruby code -+ [Client-side Routing](hyper-router.md) a Ruby DSL which wraps ReactRouter -+ [Stores](hyper-store.md) for application level state and Component communication -+ [Elements and Rendering](elements-and-rendering.md) details of the underlying mechanisms. -+ [Further Reading](further-reading.md) on React and Opal +The following chapters cover these aspects and more in detail. diff --git a/docs/client-dsl/component-basics.md b/docs/client-dsl/component-basics.md index 9c69b58c0..6f4c052d3 100644 --- a/docs/client-dsl/component-basics.md +++ b/docs/client-dsl/component-basics.md @@ -1,15 +1,6 @@ -# HyperComponent DSL - Basics - The Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface. -The DSL has the following major areas: - -* The `HyperComponent` class -* HTML DSL elements -* Component Lifecycle Methods \(`before_mount`, `after_mount`, `after_update`\) -* The `param` and `render` methods -* Event handlers -* Miscellaneous methods +The following sections give a brief orientation to the structure of a component and the methods that are used to define and control its behavior. ## Defining a Component @@ -24,7 +15,7 @@ end ## The `render` Callback -At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components](notes.md#abstract-and-concrete-components)** +At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components...](notes.md#abstract-and-concrete-components)** ```ruby class Component < HyperComponent @@ -55,9 +46,7 @@ class FirstComponent < HyperComponent end ``` -Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method. - -> The one exception to using `new` is within a spec to create a "headless" component in order to access its internal state and methods. +> While a component is defined as a class, and a rendered component is an instance of that class, we do not in general use the `new` method, or need to modify the components `initialize` method. ### Invoking Components @@ -68,15 +57,76 @@ MyCustomComponent {} # ok MyCustomComponent # <--- breaks > ``` -## Multiple Components +## Component Params + +A component can receive params to customize its look and behavior: + +```Ruby +class SayHello < HyperComponent + param :to + render(DIV, class: :hello) do + "Hello #{to}!" + end +end + +... + + SayHello(to: "Joe") +``` + +Components can receive new params, causing the component to update. **[More on Params ...](params.md)**. + +## Component State + +Component also have *state*, which is stored in instance variables. You signal a state change using the `mutate` method. Component state is a fundamental concept covered **[here](state.md)**. + + +## Life Cycle Callbacks + +A component may be updated during its life time due to either changes in state or receiving new params. You can hook into the components life cycle using the +the life cycle methods. Two of the most common lifecycle methods are `before_mount` and `after_mount` that are called before a component first renders, and +just after a component first renders respectively. + +```RUBY +class Clock < HyperComponent + param format: "%m/%d/%Y %I:%M:%S" + after_mount do + every(1.second) { mutate @current_time = Time.now } + end + render do + DIV { @current_time.strftime(format) } + end +end +``` + +The complete list of life cycle methods and their syntax is discussed in detail in the **[Lifecycle Methods](/lifecycle-methods)** section. + +## Events, Event Handlers, and Component Callbacks + +Events such as mouse clicks trigger callbacks, which can be attached using the `on` method: + +```ruby +class ClickCounter < HyperComponent + before_mount { @clicks = 0 } + def adverb + @clicks.zero? ? 'please' : 'again' + end + render(DIV) do + BUTTON { "click me #{adverb}" } + .on(:click) { mutate @clicks += 1 } # attach a callback + DIV { "I've been clicked #{pluralize(@clicks, 'time')}" } if @clicks > 0 + end +end +``` -So far, we've looked at how to write a single component to display data. Next let's examine how components are combined to build an application. +This example also shows how events and state mutations work together to change the look of the display. It also demonstrates that because a HyperComponent +is just a Ruby class you can define helper methods, use conditional logic, and call on predefined methods like `pluralize`. -By building modular components that reuse other components with well-defined interfaces you can _separate the different concerns_ of your app. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain. +In addition components can fire custom events, and make callbacks to the upper level components. **[More details ...](events-and-callbacks.md)** -### Composition Example +## Application Structure -Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API. +Your Application is built out of many smaller components using the above features to control the components behavior and communicate between components. To conclude this section let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API. ```ruby class Avatar < HyperComponent diff --git a/docs/client-dsl/component-details.md b/docs/client-dsl/component-details.md index 73b387a01..3b4f27365 100644 --- a/docs/client-dsl/component-details.md +++ b/docs/client-dsl/component-details.md @@ -1,5 +1,3 @@ -## Children, Keys, and Fragments - ### Children Components often have child components. If you consider HTML tags like `DIV`, `UL`, and `TABLE` @@ -11,13 +9,16 @@ DIV(id: 1) do SPAN(class: :span_2) { 'there' } end ``` -Here we have a `DIV` that receives one param, an id equal to 1 *and* has two child *elements* - the two spans. +Here we have a `DIV` that receives one param, an id equal to 1 and has two child *elements* - the two spans. -The `SPAN`s each have one param (its class) and has one child *element* - a string to render. +The `SPAN`s each have one param (its css class) and has one child *element* - a string to render. -Hopefully the DSL is intuitive to read, and you can see that this will generate the following HTML: +Hopefully at this point the DSL is intuitive to read, and you can see that this will generate the following HTML: ```HTML -
    hithere
    +
    + hi + there +
    ``` ### Dynamic Children @@ -33,7 +34,7 @@ UL do end end ``` -Here we don't determine the actual number or contents of the `LI` children until runtime. +In this case you can see that we don't determine the actual number or contents of the `LI` children until runtime. >**[The `word_count` method...](notes.md#word-count-method)** @@ -42,16 +43,16 @@ Here we don't determine the actual number or contents of the `LI` children until ### Keys In the above example what would happen if the contents of `text` were dynamically changing? For -example if it was associated with a text box that user was typing into, and we updated `text` +example if it was associated with a text box that the user was typing into, and we updated `text` whenever a word was entered. In this case as the user typed new words, the `word_count` would be updated and the list would change. -However actually only the contents of one of list items (`LI` blocks) would actually change, and +However actually only the contents of one of the list items (`LI` blocks) would actually change, and perhaps the sort order. We don't need to redraw the whole list, just the one list item that changed, and then perhaps shuffle two of the items. This is going to be much faster than redrawing the whole list. -Like React, Hyperstack provides a special key param that you can identify child elements so that the +Like React, Hyperstack provides a special `key` param that can identify child elements so that the rendering engine will know that while the content and order may change on some children, it can easily identify the ones that are the same: @@ -64,42 +65,9 @@ mind when you are generating long lists, tables, and divs with many children. > **[More on how Hyperstack generates keys...](notes.md#generating-keys)** - - -### Child Reconciliation - -**Reconciliation is the process by which the underlying React engine updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass the items will be regenerated and then merged into the DOM by React. - -```ruby -param :items -render do - items.each do |item| - P { item[:text] } - end -end -``` - -What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`? Intuitively, the paragraph `

    foo

    ` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the _order_ of the children. - -### Dynamic Children - -The situation gets more complicated when the children are shuffled around \(as in search results\) or if new components are added onto the front of the list \(as in streams\). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a `key`: - -```ruby - param :results, type: [Hash] # each result is a hash of the form {id: ..., text: ....} - render do - OL do - results.each do |result| - LI(key: result[:id]) { result[:text] } - end - end - end -``` - -When React reconciles the keyed children, it will ensure that any child with `key` will be reordered \(instead of clobbered\) or destroyed \(instead of reused\). - ### Rendering Children +Application defined components can also receive and render children. A component's `children` method returns an enumerable that is used to access the *unrendered* children of a component. The children can then be rendered using the `render` method which will merge any additional parameters and render the child. @@ -107,7 +75,7 @@ render the child. ```ruby class Indenter < HyperComponent render(DIV) do - IndentEachLine(by: 10) do # see IndentEachLine below + IndentLine(by: 10) do # see IndentLine below DIV {"Line 1"} DIV {"Line 2"} DIV {"Line 3"} @@ -115,7 +83,7 @@ class Indenter < HyperComponent end end -class IndentEachLine < HyperComponent +class IndentLine < HyperComponent param by: 20, type: Integer render(DIV) do @@ -158,203 +126,3 @@ class ListItems < HyperComponent end end ``` - -The only param that FRAGMENT may take is a key, which is useful if there will be multiple fragments being merged at some higher level. - - -### Data Flow - -In React, data flows from owner to owned component through the params as discussed above. This is effectively one-way data binding: owners bind their owned component's param to some value the owner has computed based on its `params` or `state`. Since this process happens recursively, data changes are automatically reflected everywhere they are used. - -### Stores - -Managing state between components is best done using Stores as many Components can access one store. This saves passing data between Components. Please see the [Store documentation](https://docs.hyperstack.org/client-dsl/hyper-store) for details. - -### Reusable Components - -When designing interfaces, break down the common design elements \(buttons, form fields, layout components, etc.\) into reusable components with well-defined interfaces. That way, the next time you need to build some UI, you can write much less code. This means faster development time, fewer bugs, and fewer bytes down the wire. - -## Params - -The `param` method gives _read-only_ access to each of the scalar params passed to the Component. Params are accessed as instance methods on the Component. - -Within a React Component the `param` method is used to define the parameter signature of the component. You can think of params as the values that would normally be sent to the instance's `initialize` method, but with the difference that a React Component gets new parameters when it is re-rendered. - -Note that the default value can be supplied either as the hash value of the symbol, or explicitly using the `:default_value` key. - -Examples: - -```ruby -param :foo # declares that we must be provided with a parameter foo when the component is instantiated or re-rerendered. -param :foo, alias: :something # the alias name will be used for the param (instead of Foo) -param :foo => "some default" # declares that foo is optional, and if not present the value "some default" will be used. -param foo: "some default" # same as above using ruby 1.9 JSON style syntax -param :foo, default: "some default" # same as above but uses explicit default key -param :foo, type: String # foo is required and must be of type String -param :foo, type: [String] # foo is required and must be an array of Strings -param foo: [], type: [String] # foo must be an array of strings, and has a default value of the empty array. -``` - -#### Accessing param values - -Params are accessible in the Component class as instance methods. - -For example: - -```ruby -class Hello < HyperComponent - # an immutable parameter, with a default of type String - param visitor: "World", type: String - - render do - "Hello #{visitor}" - end -end -``` - -### Immutable params - -A core design concept taken from React is that data flows down to child Components via params and params \(called props in React\) are immutable. - -However for complex objects - -In Hyperstack, there are **two exceptions** to this rule: - -* An instance of a **Store** \(passed as a param\) is mutable and changes to the state of the Store will cause a re-render -* An instance of a **Model** \(discussed in the Isomorphic section of these docs\) will also case a re-render when changed - -In the example below, clicking on the button will cause the Component to re-render \(even though `book` is a `param`\) because `book` is a Model. If `book` were not a Model \(or Store\) then the Component would not re-render. - -```ruby -class Likes < HyperComponent - param :book # book is an instance of the Book model - - render(DIV) do - P { "#{book.likes.count} likes" } - BUTTON { "Like" }.on(:click) { book.likes += 1} - end -end -``` - -> Note: Non-scalar params \(objects\) which are mutable through their methods are not read only. Care should be taken here as changes made to these objects will **not** cause a re-render of the Component. Specifically, if you pass a non-scalar param into a Component, and modify the internal data of that param, Hyperstack will not be notified to re-render the Component \(as it does not know about the internal structure of your object\). To achieve a re-render in this circumstance you will need to ensure that the parts of your object which are mutable are declared as state in a higher-order parent Component so that data can flow down from the parent to the child as per the React pattern. - -### Param Validation - -As your app grows it's helpful to ensure that your components are used correctly. We do this by allowing you to specify the expected ruby class of your parameters. When an invalid value is provided for a param, a warning will be shown in the JavaScript console. Note that for performance reasons type checking is only done in development mode. Here is an example showing typical type specifications: - -```ruby -class ManyParams < HyperComponent - param :an_array, type: [] # or type: Array - param :a_string, type: String - param :array_of_strings, type: [String] - param :a_hash, type: Hash - param :some_class, type: SomeClass # works with any class - param :a_string_or_nil, type: String, allow_nil: true -end -``` - -Note that if the param can be nil, add `allow_nil: true` to the specification. - -### Default Param Values - -React lets you define default values for your `params`: - -```ruby -class ManyParams < HyperComponent - param :an_optional_param, default: "hello", type: String, allow_nil: true -``` - -If no value is provided for `:an_optional_param` it will be given the value `"hello"` - -### Params of type Proc - -A Ruby `Proc` can be passed to a component like any other object. - -```ruby -param :all_done, type: Proc -... - # typically in an event handler -all_done(data).call -``` - -Proc params can be optional, using the `default: nil` and `allow_nil: true` options. Invoking a nil proc param will do nothing. This is handy for allowing optional callbacks. - -```ruby -class Alarm < HyperComponent - param :at, type: Time - param :notify, type: Proc - - after_mount do - @clock = every(1) do - if Time.now > at - notify.call - @clock.stop - end - force_update! - end - end - - render do - "#{Time.now}" - end -end -``` - -If for whatever reason you need to get the actual proc instead of calling it use `params.method(*symbol name of method*)` - -### Components as Params - -You can pass a Component as a `param` and then render it in the receiving Component. To create a Component without rendering it you use `.as_node`. This technique is used extensively in JavaScript libraries. - -```ruby -# in the parent Component... -button = MyButton().as_node -ButtonBar(button: button) - -class ButtonBar < HyperComponent - param :button - - render do - button.render - end -end -``` - -`as_node` can be attached to a component or tag, and removes the element from the rendering buffer and returns it. This is useful when you need store an element in some data structure, or passing to a native JS component. When passing an element to another Hyperstack Component `.as_node` will be automatically applied so you normally don't need it. - -`render` can be applied to the objects returned by `as_node` and `children` to actually render the node. - -```ruby -class Test < HyperComponent - param :node - - render do - DIV do - children.each do |child| - node.render - child.render - end - node.render - end - end -end -``` - -### Other Params - -A common type of React component is one that extends a basic HTML element in a simple way. Often you'll want to copy any HTML attributes passed to your component to the underlying HTML element. - -To do this use the `collect_other_params_as` method which will gather all the params you did not declare into a hash. Then you can pass this hash on to the child component - -```ruby -class CheckLink < HyperComponent - collect_other_params_as :attributes - render do - # we just pass along any incoming attributes - a(attributes) { '√ '.span; children.each &:render } - end -end -# CheckLink(href: "/checked.html") -``` - -Note: `collect_other_params_as` builds a hash, so you can merge other data in or even delete elements out as needed. diff --git a/docs/client-dsl/elements-and-rendering.md b/docs/client-dsl/elements-and-rendering.md index acc024af4..f7d075984 100644 --- a/docs/client-dsl/elements-and-rendering.md +++ b/docs/client-dsl/elements-and-rendering.md @@ -1,5 +1,7 @@ # Elements and Rendering +document as_node here + ### React.create\_element **Note: You almost never need to directly call `create_element`, the DSL, Rails, and jQuery interfaces take care of this for you.** @@ -158,4 +160,3 @@ DIV("data-custom-attribute" => "foo") ```ruby DIV("aria-hidden" => true) ``` - diff --git a/docs/client-dsl/event-handlers.md b/docs/client-dsl/event-handlers.md deleted file mode 100644 index 0b93681f2..000000000 --- a/docs/client-dsl/event-handlers.md +++ /dev/null @@ -1,298 +0,0 @@ -# Event Handlers - -Event Handlers are attached to tags and components using the `on` method. - -```ruby -SELECT ... do - ... -end.on(:change) do |e| - mutate mode = e.target.value.to_i -end -``` - -The `on` method takes the event name symbol \(note that `onClick` becomes `:click`\) and the block is passed the React.js event object. - -```ruby -BUTTON { 'Press me' }.on(:click) { do_something } -# you can add an event handler to any HTML element -H1(class: :cursor_hand) { 'Click me' }.on(:click) { do_something } -``` - -Event handlers can be chained like so - -```ruby -INPUT ... do - ... - end.on(:key_up) do |e| - ... - end.on(:change) do |e| - ... -end -``` - -### Event Handling and Synthetic Events - -With React you attach event handlers to elements using the `on` method. React ensures that all events behave identically in IE8 and above by implementing a synthetic event system. That is, React knows how to bubble and capture events according to the spec, and the events passed to your event handler are guaranteed to be consistent with [the W3C spec](http://www.w3.org/TR/DOM-Level-3-Events/), regardless of which browser you're using. - -### Under the Hood: Event Delegation - -React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see [David Walsh's excellent blog post](http://davidwalsh.name/event-delegate). - -### React::Event - -Your event handlers will be passed instances of `React::Event`, a wrapper around react.js's `SyntheticEvent` which in turn is a cross browser wrapper around the browser's native event. It has the same interface as the browser's native event, including `stopPropagation()` and `preventDefault()`, except the events work identically across all browsers. - -For example: - -```ruby -class YouSaid < HyperComponent - - render(DIV) do - INPUT(value: state.value). - on(:key_down) do |e| - alert "You said: #{state.value}" if e.key_code == 13 - end. - on(:change) do |e| - mutate value = e.target.value - end - end -end -``` - -If you find that you need the underlying browser event for some reason use the `native_event`. - -In the following responses shown as \(native ...\) indicate the value returned is a native object with an Opal wrapper. In some cases there will be opal methods available \(i.e. for native DOMNode values\) and in other cases you will have to convert to the native value with `.to_n` and then use javascript directly. - -Every `React::Event` has the following methods: - -```ruby -bubbles -> Boolean -cancelable -> Boolean -current_target -> (native DOM node) -default_prevented -> Boolean -event_phase -> Integer -is_trusted -> Boolean -native_event -> (native Event) -prevent_default -> Proc -is_default_prevented -> Boolean -stop_propagation -> Proc -is_propagation_stopped -> Boolean -target -> (native DOMEventTarget) -timestamp -> Integer (use Time.at to convert to Time) -type -> String -``` - -### Event pooling - -The underlying React `SyntheticEvent` is pooled. This means that the `SyntheticEvent` object will be reused and all properties will be nullified after the event method has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way. - -### Supported Events - -React normalizes events so that they have consistent properties across different browsers. - -### Clipboard Events - -Event names: - -```ruby -:copy, :cut, :paste -``` - -Available Methods: - -```ruby -clipboard_data -> (native DOMDataTransfer) -``` - -### Composition Events \(not tested\) - -Event names: - -```ruby -:composition_end, :composition_start, :composition_update -``` - -Available Methods: - -```ruby -data -> String -``` - -### Keyboard Events - -Event names: - -```ruby -:key_down, :key_press, :key_up -``` - -Available Methods: - -```ruby -alt_key -> Boolean -char_code -> Integer -ctrl_key -> Boolean -get_modifier_state(key) -> Boolean (i.e. get_modifier_key(:Shift) -key -> String -key_code -> Integer -locale -> String -location -> Integer -meta_key -> Boolean -repeat -> Boolean -shift_key -> Boolean -which -> Integer -``` - -### Focus Events - -Event names: - -```ruby -:focus, :blur -``` - -Available Methods: - -```ruby -related_target -> (Native DOMEventTarget) -``` - -These focus events work on all elements in the React DOM, not just form elements. - -### Form Events - -Event names: - -```ruby -:change, :input, :submit -``` - -### Mouse Events - -Event names: - -```ruby -:click, :context_menu, :double_click, :drag, :drag_end, :drag_enter, :drag_exit -:drag_leave, :drag_over, :drag_start, :drop, :mouse_down, :mouse_enter, -:mouse_leave, :mouse_move, :mouse_out, :mouse_over, :mouse_up -``` - -The `:mouse_enter` and `:mouse_leave` events propagate from the element being left to the one being entered instead of ordinary bubbling and do not have a capture phase. - -Available Methods: - -```ruby -alt_key -> Boolean -button -> Integer -buttons -> Integer -client_x -> Integer -number client_y -> Integer -ctrl_key -> Boolean -get_modifier_state(key) -> Boolean -meta_key -> Boolean -page_x -> Integer -page_y -> Integer -related_target -> (Native DOMEventTarget) -screen_x -> Integer -screen_y -> Integer -shift_key -> Boolean -``` - -### Drag and Drop example - -Here is a Hyperstack version of this [w3schools.com](https://www.w3schools.com/html/html5_draganddrop.asp) example: - -```ruby -DIV(id: "div1", style: {width: 350, height: 70, padding: 10, border: '1px solid #aaaaaa'}) - .on(:drop) do |ev| - ev.prevent_default - data = `#{ev.native_event}.native.dataTransfer.getData("text")` - `#{ev.target}.native.appendChild(document.getElementById(data))` - end - .on(:drag_over) { |ev| ev.prevent_default } - -IMG(id: "drag1", src: "https://www.w3schools.com/html/img_logo.gif", draggable: "true", width: 336, height: 69) - .on(:drag_start) do |ev| - `#{ev.native_event}.native.dataTransfer.setData("text", #{ev.target}.native.id)` - end -``` - -### Selection events - -Event names: - -```ruby -onSelect -``` - -### Touch events - -Event names: - -```ruby -:touch_cancel, :touch_end, :touch_move, :touch_start -``` - -Available Methods: - -```ruby -alt_key -> Boolean -changed_touches -> (Native DOMTouchList) -ctrl_key -> Boolean -get_modifier_state(key) -> Boolean -meta_key -> Boolean -shift_key -> Boolean -target_touches -> (Native DOMTouchList) -touches -> (Native DomTouchList) -``` - -### UI Events - -Event names: - -```ruby -:scroll -``` - -Available Methods: - -```ruby -detail -> Integer -view -> (Native DOMAbstractView) -``` - -### Wheel Events - -Event names: - -```ruby -wheel -``` - -Available Methods: - -```ruby -delta_mode -> Integer -delta_x -> Integer -delta_y -> Integer -delta_z -> Integer -``` - -### Media Events - -Event names: - -```ruby -:abort, :can_play, :can_play_through, :duration_change,:emptied, :encrypted, :ended, :error, :loaded_data, -:loaded_metadata, :load_start, :pause, :play, :playing, :progress, :rate_change, :seeked, :seeking, :stalled, -:on_suspend, :time_update, :volume_change, :waiting -``` - -### Image Events - -Event names: - -```ruby -:load, :error -``` - diff --git a/docs/client-dsl/html-css.md b/docs/client-dsl/html-css.md index 0d5adfa36..4dca9ec02 100644 --- a/docs/client-dsl/html-css.md +++ b/docs/client-dsl/html-css.md @@ -2,13 +2,14 @@ ### HTML elements -A Hyperstack user-interface is composed of HTML elements, conditional logic and Components. +Each Hyperstack component renders a series of HTML (and SVG) elements, using Ruby expressions to control the output. ```ruby UL do 5.times { |n| LI { "Number #{n}" }} end ``` + For example ```ruby diff --git a/docs/client-dsl/hyper-router.md b/docs/client-dsl/hyper-router.md deleted file mode 100644 index 96dd377f6..000000000 --- a/docs/client-dsl/hyper-router.md +++ /dev/null @@ -1,430 +0,0 @@ -# Client-side Routing - -HyperRouter is a DSL wrapper for [ReactRouter v4.x](https://github.com/ReactTraining/react-router) to provide client-side routing for Single Page Applications (SPA). - -## Usage - -```ruby -class AppRouter - include Hyperstack::Component - include Hyperstack::Router::Helpers - include Hyperstack::Router - - render(DIV) do - UL do - LI { Link('/') { 'Home' } } - LI { Link('/about') { 'About' } } - end - Route('/', exact: true, mounts: Home) - Route('/about', mounts: About) - end -end - -class Home - include Hyperstack::Component - render(DIV) do - H2 { 'Home' } - end -end -``` - -## DSL - -### Router - -This is the Router module which you include in your top level component: - -```ruby -class MyRouter - include Hyperstack::Component - include Hyperstack::Router -end -``` - -With the base Router class, you can also specify the history you want to use. - -This can be done either using a macro: - -```ruby -class MyRouter - include Hyperstack::Component - include Hyperstack::Router - - history :browser # this is the default option if no other is specified -end -``` - -The macro accepts three options: `:browser`, `:hash`, or `:memory`. - -Or by defining the `history` method: - -```ruby -class MyRouter - include Hyperstack::Component - include Hyperstack::Router - - def history - self.class.browser_history - end -end -``` - -### Rendering a Router - -Use the `render` macro as normal. Note you cannot redefine the `render` instance method in a Router componenent - -```ruby -class MyRouter - ... - - render(DIV) do - H1 { 'Hello world!' } - end -end -``` - -### Routes - -Routes are defined with special pseudo components you call inside the router/components. The router determines which of the routes to actually mount based on the current URL. - -```ruby -class MyRouter - ... - - render(DIV) do - Route('/', mounts: HelloWorld) - end -end - -class HelloWorld - render do - H1 { 'Hello world!' } - end -end -``` - -The `Route` method takes a url path, and these options: - -* `mounts: Component` The component you want to mount when routed to -* `exact: Boolean` When true, the path must match the location exactly -* `strict: Boolean` When true, the path will only match if the location and path **both** have/don't have a trailing slash - -The `Route` method can also take a block instead of the `mounts` option. - -```ruby -class MyRouter - ... - - render(DIV) do - Route('/', exact: true) do - H1 { 'Hello world!' } - end - end -end -``` - -The block will be given the match, location, and history data: - -```ruby -class MyRouter - ... - - render(DIV) do - Route('/:name') do |match, location, history| - H1 { "Hello #{match.params[:name]} from #{location.pathname}, click me to go back!" } - .on(:click) { history.go_back } - end - end -end -``` - -* The `Hyperstack::Router::Helpers` is useful for components mounted by the router. -* This automatically sets the `match`, `location`, and `history` params, - - and also gives you instance methods with those names. - -* You can use either `params.match` or just `match`. - - and gives you access to the `Route` method and more. - -* This allows you to create inner routes as you need them. - -```ruby -class MyRouter - include Hyperstack::Component - include Hyperstack::Router::Helpers - include Hyperstack::Router - - render(DIV) do - Route('/:name', mounts: Greet) - end -end - -class Greet - include Hyperstack::Component - include Hyperstack::Router::Helpers - - render(DIV) do - H1 { "Hello #{match.params[:foo]}!" } - Route(match.url, exact: true) do - H2 { 'What would you like to do?' } - end - Route("#{match.url}/:activity", mounts: Activity) - end -end - -class Activity - include Hyperstack::Component - include Hyperstack::Router::Helpers - include Hyperstack::Router - - render(DIV) do - H2 { params.match.params[:activity] } - end -end -``` - -Normally routes will **always** render alongside sibling routes that match as well. - -```ruby -class MyRouter - ... - - render(DIV) do - Route('/goodbye', mounts: Goodbye) - Route('/:name', mounts: Greet) - end -end -``` - -### Switch - -Going to `/goodbye` would match `/:name` as well and render `Greet` with the `name` param with the value 'goodbye'. To avoid this behavior and only render one matching route at a time, use a `Switch` component. - -```ruby -class MyRouter - ... - - render(DIV) do - Switch do - Route('/goodbye', mounts: Goodbye) - Route('/:name', mounts: Greet) - end - end -end -``` - -Now, going to `/goodbye` would match the `Goodbye` route first and only render that component. - -### Links - -Links are provided by both the `Hyperstack::Router` and `Hyperstack::Router::Helper` modules. - -The `Link` method takes a url path, and these options: - -* `search: String` adds the specified string to the search query -* `hash: String` adds the specified string to the hash location - - it can also take a block of children to render inside it. - -```ruby -class MyRouter - ... - - render(DIV) do - Link('/Gregor Clegane') - - Route('/', exact: true) { H1() } - Route('/:name') do |match| - H1 { "Will #{match.params[:name]} eat all the chickens?" } - end - end -end -``` - -### NavLinks - -NavLinks are the same as Links, but will add styling attributes when it matches the current url - -* `active_class: String` adds the class to the link when the url matches -* `active_style: String` adds the style to the link when the url matches -* `active: Proc` A proc that will add extra logic to determine if the link is active - -```ruby -class MyRouter - ... - - render(DIV) do - NavLink('/Gregor Clegane', active_class: 'active-link') - NavLink('/Rodrik Cassel', active_style: { color: 'grey' }) - NavLink('/Oberyn Martell', - active: ->(match, location) { - match && match.params[:name] && match.params[:name] =~ /Martell/ - }) - - Route('/', exact: true) { H1() } - Route('/:name') do |match| - H1 { "Will #{match.params[:name]} eat all the chickens?" } - end - end -end -``` - -### Pre-rendering - -Pre-rendering is automatically taken care for you under the hood. - -## Setup - -To setup HyperRouter: - -* Install the gem -* Your page should render your router as its top-level-component \(first component to be rendered on the page\) - in the example below this would be `AppRouter` -* You will need to configure your server to route all unknown routes to the client-side router \(Rails example below\) - -### With Rails - -Assuming your router is called `AppRouter`, add the following to your `routes.rb` - -```ruby -root 'Hyperstack#AppRouter' # see note below -match '*all', to: 'Hyperstack#AppRouter', via: [:get] # this should be the last line of routes.rb -``` - -Note: - -`root 'Hyperstack#AppRouter'` is shorthand which will automagically create a Controller, View and launch `AppRouter` as the top-level Component. If you are rendering your Component via your own COntroller or View then ignore this line. - -### Example - -Here is the basic JSX example that is used on the [react-router site](https://reacttraining.com/react-router/) - -```jsx -import React from 'react' -import { - BrowserRouter as Router, - Route, - Link -} from 'react-router-dom' - -const BasicExample = () => ( - -
    -
      -
    • Home
    • -
    • About
    • -
    • Topics
    • -
    - -
    - - - - -
    -
    -) - -const Home = () => ( -
    -

    Home

    -
    -) - -const About = () => ( -
    -

    About

    -
    -) - -const Topics = ({ match }) => ( -
    -

    Topics

    -
      -
    • Rendering with React
    • -
    • Components
    • -
    • Props v. State
    • -
    - - - ( -

    Please select a topic.

    - )}/> -
    -) - -const Topic = ({ match }) => ( -
    -

    {match.params.topicId}

    -
    -) - -export default BasicExample -``` - -And here is the same example in Hyperstack: - -```ruby -class BasicExample - include Hyperstack::Component - include Hyperstack::Router::Helpers - include Hyperstack::Router - - render(DIV) do - UL do - LI { Link('/') { 'Home' } } - LI { Link('/about') { 'About' } } - LI { Link('/topics') { 'Topics' } } - end - - Route('/', exact: true, mounts: Home) - Route('/about', mounts: About) - Route('/topics', mounts: Topics) - end -end - -class Home - include Hyperstack::Component - include Hyperstack::Router::Helpers - - render(DIV) do - H2 { 'Home' } - end -end - -class About - include Hyperstack::Component - include Hyperstack::Router::Helpers - - render(:div) do - H2 { 'About' } - end -end - -class Topics - include Hyperstack::Component - include Hyperstack::Router::Helpers - - render(DIV) do - H2 { 'Topics' } - UL() do - LI { Link("#{match.url}/rendering") { 'Rendering with React' } } - LI { Link("#{match.url}/components") { 'Components' } } - LI { Link("#{match.url}/props-v-state") { 'Props v. State' } } - end - Route("#{match.url}/:topic_id", mounts: Topic) - Route(match.url, exact: true) do - H3 { 'Please select a topic.' } - end - end -end - -class Topic - include Hyperstack::Component - include Hyperstack::Router::Helpers - - render(:div) do - H3 { match.params[:topic_id] } - end -end -``` diff --git a/docs/client-dsl/hyper-store.md b/docs/client-dsl/hyper-store.md deleted file mode 100644 index 2ea22eba0..000000000 --- a/docs/client-dsl/hyper-store.md +++ /dev/null @@ -1,123 +0,0 @@ -# Stores - -A core concept behind React is that Components contain their own state and pass state down to their children as params. React re-renders the interface based on those state changes. Each Component is discreet and only needs to worry about how to render itself and pass state down to its children. - -Sometimes however, at an application level, Components need to be able to share information or state in a way which does not adhere to this strict parent-child relationship. - -Some examples of where this can be necessary are: - -* Where a child needs to pass a message back to its parent. An example would be if the child component is an item in a list, it might need to inform it's parent that it has been clicked on. -* When Hyperstack models are passed as params, child components might change the values of fields in the model, which might be rendered elsewhere on the page. -* There has to be a place to store non-persisted, global application-level data; like the ID of the currently logged in user or a preference or variable that affects the whole UI. - -Taking each of these examples, there are ways to accomplish each: - -* Child passing a message to parent: the easiest way is to pass a `Proc` as a param to the child from the parent that the child can `call` to pass a message back to the parent. This model works well when there is a simple upward exchange of information \(a child telling a parent that it has been selected for example\). You can read more about Params of type Proc in the Component section of these docs. If howevere, you find yourself adding overusing this method, or passing messages from child to grandparent then you have reached the limits of this method and a Store would be a better option \(read about Stores in this section.\) -* Models are stores. An instance of a model can be passed between Components, and any Component using the data in a Model to render the UI will re-render when the Model data changes. As an example, if you had a page displaying data from a Model and let's say you have an edit button on that page \(which invokes a Dialog \(Modal\) based Component which receives the model as a param\). As the user edits the Model fields in Dialog, the underlying page will show the changes as they are made as the changes to Model fields will be observed by the parent Components. In this way, Models act very much like Stores. -* Stores are where global, application wide state can exist in singleton classes that all Components can access or as class instances objects which hold data and state. **A Store is a class or an instance of a class which holds state variables which can affect a re-render of any Component observing that data.** - -In technical terms, a Store is a class that includes the `include Hyperstack::State::Observable` mixin, which just adds the `mutate` and `observe` primitive methods \(plus helpers built on top of them\). - -In most cases, you will want class level instance variables that share data across components. Occasionally you might need multiple instances of a store that you can pass between Components as params \(much like a Model\). - -As an example, let's imagine we have a filter field on a Menu Bar in our application. As the user types, we want the user interface to display only the items which match the filter. As many of the Components on the page might depend on the filter, a singleton Store is the perfect answer. - -```ruby -# app/hyperstack/stores/item_store.rb -class ItemStore - include Hyperstack::State::Observable - - class << self - def filter=(f) - mutate @filter = f - end - - def filter - observe @filter || '' - end - end -end -``` - -In Our application code, we would use the filter like this: - -```ruby -# the TextField on the Menu Bar could look like this: -TextField(label: 'Filter', value: ItemStore.filter).on(:change) do |e| - ItemStore.filter = e.target.value -end - -# elsewhere in the code we could use the filter to decide if an item is added to a list -show_item(item) if item.name.include?(ItemStore.filter) -``` - -## The observe and mutate methods - -As with Components, you `mutate` an instance variable to notify React that the Component might need to be re-rendered based on the state change of that object. Stores are the same. When you `mutate` and instance variable in Store, all Components that are observing that variable will be re-rendered. - -`observe` records that a Component is observing an instance variable in a Store and might need to be re-rendered if the variable is mutated in the future. - -> If you `mutate` an instance variable outside of a Component, you need to `observe` it because, for simplicity, a Component observe their own instance vaibales. - -The `observe` and `mutate` methods take: - -* a single param as shown above -* a string of params \(`mutate a=1, b=2`\) -* or a block in which case the entire block will be executed before signalling the rest of the system -* no params \(handy for adding to the end of a method\) - -## Helper methods - -To make things easier the `Hyperstack::State::Observable` mixin contains some useful helper methods: - -The `observer` and `mutator` methods create a method wrapped in `observe` or `mutate` block. - -* `observer` -* `mutator` - -```ruby -mutator(:inc) { @count = @count + 1 } -mutator(:reset) { @count = 0 } -``` - -The `state_accessor`, `state_reader` and `state_writer` methods work just like `attr_accessor` methods except access is wrapped in the appropriate `mutate` or `observe` method. These methods can be used either at the class or instance level as needed. - -* `state_reader` -* `state_writer` -* `state_accessor` - -Finally there is the `toggle` method which does what it says on the tin. - -* `toggle` toggle\(:foo\) === mutate @foo = !@foo - -```ruby -class ClickStore - include Hyperstack::State::Observable - - class << self - observer(:count) { @count ||= 0 } - state_writer :count - mutator(:inc) { @count = @count + 1 } - mutator(:reset) { @count = 0 } - end -end -``` - -### Initializing class variables in singleton Store - -You can keep the logic around initialization in your Store. Remember that in Ruby your class instance variables can be initialized as the class is defined: - -```ruby -class CardStore - include Hyperstack::State::Observable - - @show_card_status = true - @show_card_details = false - - class << self - state_accessor :show_card_status - state_accessor :show_card_details - end -end -``` - diff --git a/docs/client-dsl/javascript-components.md b/docs/client-dsl/javascript-components.md index 8ee31bc39..652a434ee 100644 --- a/docs/client-dsl/javascript-components.md +++ b/docs/client-dsl/javascript-components.md @@ -2,11 +2,9 @@ Hyperstack gives you full access to the entire universe of JavaScript libraries and components directly within your Ruby code. -Everything you can do in JavaScript is simple to do in Ruby; this includes passing parameters between Ruby and JavaScript and even passing Ruby methods as JavaScript callbacks. See the JavaScript section for more information. +Everything you can do in JavaScript is simple to do in Opal-Ruby; this includes passing parameters between Ruby and JavaScript and even passing Ruby methods as JavaScript callbacks. See the JavaScript section for more information. -While it is quite possible to develop large applications purely in Hyperstack Components with a ruby back end like rails, you may eventually find you want to use some pre-existing React Javascript library. Or you may be working with an existing React-JS application, and want to just start adding some Hyperstack Components. - -Either way you are going to need to import Javascript components into the Hyperstack namespace. Hyperstack provides both manual and automatic mechanisms to do this depending on the level of control you need. +> **[For more information on writing Javascript within your Ruby code...](/notes.md#Javascript)** ## Importing React Components @@ -15,13 +13,23 @@ Let's say you have an existing React Component written in Javascript that you wo Here is a simple hello world component: ```javascript -window.SayHello = React.createClass({ - displayName: "SayHello", - render: function render() { - return React.createElement("div", null, "Hello ", this.props.name); +window.SayHello = class extends React.Component { + constructor(props) { + super(props); + this.displayName = "SayHello" } -}) + render() { return React.createElement("div", null, "Hello ", this.props.name); } +} +``` + +> I'm sorry I can't resist. Really? +```ruby +class SayHello < HyperComponent + param :name + render(DIV) { "Hello #{name}"} +end ``` +In what world is the Ruby not much better than that JS hot mess. Assuming that this component is loaded some place in your assets, you can then access this from Hyperstack by creating a wrapper Component: @@ -365,4 +373,3 @@ class UserDialog < HyperComponent end end ``` - diff --git a/docs/client-dsl/lifecycle-methods.md b/docs/client-dsl/lifecycle-methods.md index dc397fa7c..ee483b8e1 100644 --- a/docs/client-dsl/lifecycle-methods.md +++ b/docs/client-dsl/lifecycle-methods.md @@ -5,14 +5,16 @@ A component may define lifecycle methods for each phase of the components lifecy * `before_mount` * `render` * `after_mount` -* `before_receive_props` +* `before_new_params` * `before_update` +* `render` will be called again here * `after_update` * `before_unmount` +* `rescues` -> Note: At a minimum, one `render` method must be defined and must return just one HTML element. +All the Component Lifecycle methods (except `render`) may take a block or the name(s) of instance method(s) to be called. The `render` method always takes a block. -All the Component Lifecycle methods may take a block or the name of an instance method to be called. +The `rescues` callback is described **[here...](/error-recovery.md)** ```ruby class MyComponent < HyperComponent @@ -21,7 +23,7 @@ class MyComponent < HyperComponent end render do - # return just one HTML element + # return just some rendered components end before_unmount :cleanup # call the cleanup method before unmounting @@ -29,15 +31,11 @@ class MyComponent < HyperComponent end ``` -Except for the render method, multiple lifecycle methods may be defined for each lifecycle phase, and will be executed in the order defined, and from most deeply nested subclass outwards. - -## Lifecycle Methods - -A component class may define lifecycle methods for specific points in a component's lifecycle. +Except for `render`, multiple lifecycle callbacks may be defined for each lifecycle phase, and will be executed in the order defined, and from most deeply nested subclass outwards. Note the implication that the callbacks are inherited, which can be useful in creating **[abstract component classes](notes#abstract-and-concrete-components)**. ### Rendering -The lifecycle revolves around rendering the component. As the state or parameters of a component changes, its render method will be called to generate the new HTML. +The lifecycle revolves around rendering the component. As the state or parameters of a component change, its render callback will be executed to generate the new HTML. ```ruby render do .... @@ -62,8 +60,6 @@ render do end ``` -The purpose of the render method is syntactic. Many components consist of a static outer container with possibly some parameters, and most component's render method by necessity will be longer than the normal _10 line_ ruby style guideline. The render method solves both these problems by allowing the outer container to be specified as part of the method parameter \(which reads very nicely\) and because the render code is now specified as a block you avoid the 10 line limitation, while encouraging the rest of your methods to adhere to normal ruby style guides - ### Before Mounting \(first render\) ```ruby @@ -73,7 +69,7 @@ end Invoked once when the component is first instantiated, immediately before the initial rendering occurs. This is where state variables should be initialized. -This is the only life cycle method that is called during `render_to_string` used in server side pre-rendering. +This is the only life cycle callback run during `render_to_string` used in server side pre-rendering. ### After Mounting \(first render\) @@ -82,49 +78,29 @@ after_mount do ... end ``` -Invoked once, only on the client \(not on the server\), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children \(e.g., to access the underlying DOM representation\). The `after_mount` methods of children components are invoked before that of parent components. +Invoked once, only on the client \(not on the server during prerendering\), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children \(e.g., to access the underlying DOM representation\). The `after_mount` callbacks of child components are invoked before that of parent components. -If you want to integrate with other JavaScript frameworks, set timers using the `after` or `every` methods, or send AJAX requests, perform those operations in this method. Attempting to perform such operations in before\_mount will cause errors during prerendering because none of these operations are available in the server environment. +If you want to integrate with other JavaScript frameworks, set timers using the `after` or `every` methods, or send AJAX requests, perform those operations in this callback. Attempting to perform such operations in before\_mount will cause errors during prerendering because none of these operations are available in the server environment. ### Before Receiving New Params ```ruby -before_receive_props do |new_params_hash| ... +before_new_params do |new_params_hash| ... end ``` -Invoked when a component is receiving _new_ params \(React.js props\). This method is not called for the initial render. +Invoked when a component is receiving _new_ params \(React props\). This method is not called for the initial render. -Use this as an opportunity to react to a prop transition before `render` is called by updating any instance or state variables. The new\_props block parameter contains a hash of the new values. +Use this as an opportunity to react to receiving new param values before `render` is called by updating any instance variables. The new\_params block parameter contains a hash of the new values. ```ruby -before_receive_props do |next_props| - mutate @likes_increasing = (next_props[:like_count] > @LikeCount) +before_new_params do |next_params| + @likes_increasing = (next_params[:like_count] > like_count) end ``` > Note: There is no analogous method `before_receive_state`. An incoming param may cause a state change, but the opposite is not true. If you need to perform operations in response to a state change, use `before_update`. -TODO: The above needs to be checked and a better example provided. PR very welcome. - -### Controlling Updates - -Normally Hyperstack will only update a component if some state variable or param has changed. To override this behavior you can redefine the `should_component_update?` instance method. For example, assume that we have a state called `funky` that for whatever reason, we cannot update using the normal `state.funky!` update method. So what we can do is override `should_component_update?` call `super`, and then double check if the `funky` has changed by doing an explicit comparison. - -```ruby -class RerenderMore < HyperComponent - def should_component_update?(new_params_hash, new_state_hash) - super || new_state_hash[:funky] != state.funky - end -end -``` - -Why would this happen? Most likely there is integration between new Hyperstack Components and other data structures being maintained outside of Hyperstack, and so we have to do some explicit comparisons to detect the state change. - -Note that `should_component_update?` is not called for the initial render or when `force_update!` is used. - -> Note to react.js readers. Essentially Hyperstack assumes components are "well behaved" in the sense that all state changes will be explicitly declared using the state update \("!"\) method when changing state. This gives similar behavior to a "pure" component without the possible performance penalties. To achieve the standard react.js behavior add this line to your class `def should_component_update?; true; end` - ### Before Updating \(re-rendering\) ```ruby @@ -154,11 +130,16 @@ end Invoked immediately before a component is unmounted from the DOM. -Perform any necessary cleanup in this method, such as invalidating timers or cleaning up any DOM elements that were created in the `after_mount` method. +Perform any necessary cleanup in this method, such as cleaning up any DOM elements that were created in the `after_mount` method. Note that periodic timers and +broadcast receivers are automatically cleaned up when the component is unmounted. + +### The `before_render` and `after_render` convenience methods + +These call backs occur before and after all renders (first and re-rerenders.) They provide no added functionality but allow you to keep +your render methods focused on generating components. ### The force\_update! method `force_update!` is a component instance method that causes the component to re-rerender. This method is seldom \(if ever\) needed. The `force_update!` instance method causes the component to re-render. Usually this is not necessary as rendering will occur when state variables change, or new params are passed. - diff --git a/docs/client-dsl/notes.md b/docs/client-dsl/notes.md index 2b5d54133..489f2479d 100644 --- a/docs/client-dsl/notes.md +++ b/docs/client-dsl/notes.md @@ -58,7 +58,7 @@ Having an application wide `HyperComponent` class allows you to modify component > This is just a convention. Any class that includes the `Hyperstack::Component` module can be used as a Component. You also do not have to name it `HyperComponent`. For example some teams prefer `ApplicationComponent` more closely following the Rails convention. If you use a different name for this class be sure to set the `Hyperstack.component_base_class` setting so the -Rails generators will use the proper name when generating your components. **[more details...](/rails-installation/generators.html#specifying-the-base-class)** +Rails generators will use the proper name when generating your components. **[more details...](/rails-installation/generators.md#specifying-the-base-class)** ### Abstract and Concrete Components @@ -118,11 +118,11 @@ end ### Generating Keys Every Hyperstack object whether its a string, integer, or some complex class responds to the `to_key` method. -When you provide a component's key parameter with any object, the object's to_key method will be called, and +When you provide a component's key parameter with any object, the object's `to_key` method will be called, and return a unique key appropriate to that object. For example strings, and numbers return themselves. Other complex objects return the internal `object_id`, and -some classes provide their own `to_key` method that returns some invariant value for that class. HyperModel records +some classes provide their own `to_key` method that returns some invariant value for each instance of that class. HyperModel records return the database id for example. If you are creating your own data classes keep this in mind. You simply define a `to_key` method on the class @@ -131,7 +131,7 @@ default to the one provided by Hyperstack. ### Proper Use Of Keys -> For best results the `key` is supplied at highest level possible. (NOTE THIS MAY NO LONGER BE AN ISSUE IN LATEST REACT) +> For best results the `key` is supplied at highest level possible. (NOTE THIS MAY NO LONGER BE AN ISSUE IN LATEST REACT) ```ruby # WRONG! class ListItemWrapper < HyperComponent @@ -172,3 +172,63 @@ class MyComponent < HyperComponent end end ``` + +### Ruby Procs + +A core class of objects in Ruby is the *Proc*. A Proc (Procedure) is an +object that can be *called*. + +```Ruby +some_proc.call(1, 2, 3) +``` + +Ruby has several ways to create procs: + +```Ruby +# create a proc that will add its three parameters together +# using Proc.new +some_proc = Proc.new { |a, b, c| a + b + c } +# using the lambda method: +some_proc = lambda { |a, b, c| a + b + c } +# or the hash rocket notation: +some_proc = -> (a, b, c) { a + b + c } +# using a method (assuming self responds to foo) +some_proc = method(:foo) +``` + +And there are several more ways, each with its differences and uses. You can +find lots of details on Procs by searching online. **[Here is a good article to get you started...](https://blog.appsignal.com/2018/09/04/ruby-magic-closures-in-ruby-blocks-procs-and-lambdas.html)** + +The most common ways you will use Procs in your Hyperstack code is to define +either lifecycle or component callbacks: + +```Ruby +class Foo < HyperComponent + before_mount :do_it_before + after_mount { puts "I did it after" } + render do + BUTTON(on_click: ->() { puts "clicked Using a lambda" } ) { "click me" } + BUTTON { "no click me" }.on(:click) { puts "clicked using the on method" } + end + def do_it_before + puts "I did it before" + end +end +``` + +The different ways of specifying callbacks allow you to keep your code clear and consise, but in the end they do the same thing. + +> Note that there are subtle differences between Proc.new and lambda, that are beyond the scope of this note. + +### Javascript + +Opal-Ruby uses the backticks and `%x{ ... }` to drop blocks of Javascript code directly into your Ruby code. + +```ruby +def my_own_console(message) + # crab-claws can be used to escape back out to Ruby + `console.log(#{message})` +end +``` + +Both the backticks and `%x{ ... }` work the same, but the `%{ ... }` notation is useful for multiple lines of code. diff --git a/docs/client-dsl/state.md b/docs/client-dsl/state.md index b4f55deb3..8aaf404e7 100644 --- a/docs/client-dsl/state.md +++ b/docs/client-dsl/state.md @@ -1,215 +1,121 @@ -# State +When a component is rendered what it displays depends on some combination of three things: -In React \(and Hyperstack\) state is mutable. Changes \(mutations\) to state variables cause Components to re-render. Where state is passed into a child Component as a `param`, it will cause a re-rendering of that child Component. Change flows from a parent to a child - change does not flow upward and this is why params are not mutable. ++ the value of the params passed to the component ++ the state of the component ++ the state of some other objects on which a component depends -State variables are normal instance variables or objects. When a state variable changes, we use the `mutate` method to get React's attention and cause a re-render. Like normal instance variables, state variables are created when they are first accessed, so there is no explicit declaration. +Whenever one of these three things change the component will need to re-render. In this section we +discuss how a component's state is managed within Hyperstack. Params were covered **[here...](params.md)** and sharing state +between components will be covered **[here...](/hyper-state.md)** -The syntax of `mutate` is simple - its `mutate` and any other number of parameters and/or a block. Normal evaluation means the parameters are going to be evaluated first, and then `mutate` gets called. +The idea of state is built into Ruby and is represented by the *instance* variables of an object instance. -* `mutate @foo = 12, @bar[:zap] = 777` executes the two assignments first, then calls mutate -* or you can say `mutate { @foo = 12; @bar[:zap] = 777 }` which is more explicit, and does the same thing +Components very often have state. For example, is an item being displayed or edited? What is the current +value of a text box? A checkbox? The time that an alarm should go off? All these are state and will be +represented as values stored somewhere in instance variables. -Here are some examples: +Lets look at a simple clock component: -```ruby -class Counter < HyperComponent - before_mount do - @count = 0 # optional initialization - end - - render(DIV) do - # note how we mutate count - BUTTON { "+" }.on(:click) { mutate @count += 1) } - P { @count.to_s } - end -end -``` - -```ruby -class LikeButton < HyperComponent - render(DIV) do - BUTTON do - "You #{@liked ? 'like' : 'haven\'t liked'} this. Click to toggle." - end.on(:click) do - mutate @liked = !@liked +```RUBY +class Clock < HyperComponent + after_mount do + every(1.second) do + mutate @time = Time.now end end -end -``` - -### Components are Just State Machines - -React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent. - -In React, you simply update a component's state, and then the new UI will be rendered on this new state. React takes care of updating the DOM for you in the most efficient way. - -### What Components Should Have State? - -Most of your components should simply take some params and render based on their value. However, sometimes you need to respond to user input, a server request or the passage of time. For this you use state. - -**Try to keep as many of your components as possible stateless.** By doing this you'll isolate the state to its most logical place and minimize redundancy, making it easier to reason about your application. - -A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via `param`s. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way. -State can be held in any object \(not just a Component\). For example: - -```ruby -class TestIt - def self.swap_state - @@test = !@@test - end - - def self.result - @@test ? 'pass' : 'fail' - end -end - -class TestResults < HyperComponent - render(DIV) do - P { "Test is #{TestIt.result}" } - BUTTON { 'Swap' }.on(:click) do - mutate TestIt::swap_state - end - end + render(DIV) { "The time is #{@time}" } end ``` -In the example above, the singleton class `TestIt` holds its own internal state which is changed through a `swap_state` class method. The `TestResults` Component has no knowledge of the internal workings of the `TestIt` class. +The after_mount call back sets up a periodic timer that goes off every second and updates the the +`@time` instance variable with the current time. The assignment to `@time` is wrapped in the `mutate` method +which signals the React Engine that the state of `Clock` has been mutated, this in turn will add `Clock` to +the list of components that need to be re-rendered. -When the BUTTON is pressed, we call `mutate`, passing the object which is being mutated. The actual mutated value is not important, it is the fact that the _observed_ object \(our `TestIt` class\) is being mutated that will cause a re-render of the _observing_ `TestResults` Component. Think about `mutate` as a way of telling React that the Component needs to be re-rendered as the state has changed. +### It's that Simple Really -In the example above, we could also move the _observing_ and _mutating_ behaviour out of the Component completely and manage it in the `TestIt` class - in this case, we would call it a Store. Stores are covered in the Hyper-Store documentation later. +To reiterate: Components (and other Ruby objects) have state, and the state + the params will determine what +is rendered. When state changes we signal this using the mutate method, and any components depending on the state +will be re-rendered. -### What Should Go in State? +### State Mutation Always Drives Rendering -**State should contain data that a component's instance variables, event handlers, timers, or http requests may change and trigger a UI update.** +It is always a mutation of state that triggers the UI to begin a render cycle. That mutation may in turn cause components +to render and send different params to lower level components, but it begins with a state mutation. -When building a stateful component, think about the minimal possible representation of its state, and only store those properties in `state`. Add to your class methods to compute higher level values from your state variables. Avoid adding redundant or computed values as state variables as these values must then be kept in sync whenever state changes. +### What Causes State To Mutate? -### What Shouldn't Go in State? +Right! Good question! State is mutated by your code's reaction to some external event. A button click, text being typed, +or the arrival of data from the server. We will cover these in upcoming sections, but once one an event occurs your +code will probably mutate some state as a result, causing component depending on this state to update. -State should contain the minimal amount of data needed to represent your UI's state. As such, it should not contain: +### Details on the `mutate` Syntax -* **Computed data:** Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation during rendering. For example, if you have an array of list items in state and you want to render the count as a string, simply render `"#{@list_items.length} list items'` in your `render` method rather than storing the count as another state. -* **Data that does not effect rendering:** Changing an instance variable \(or any object\) that does not affect rendering does not need to be mutated \(i.e you do not need to call `mutate`\). +The main purpose of `mutate` is to signal that state has changed, but it also useful to clarify how your code works. +Therefore `mutate` can be used in a number of flexible ways: -The rule is simple: anytime you are updating a state variable use `mutate` and your UI will be re-rendered appropriately. - -### State and user input - -Often in a UI you gather input from a user and re-render the Component as they type. For example: - -```ruby -class UsingState < HyperComponent - - render(DIV) do - # the button method returns an HTML element - # .on(:click) is an event handeler - # notice how we use the mutate method to get - # React's attention. This will cause a - # re-render of the Component - button.on(:click) { mutate(@show = !@show) } - DIV do - input - output - easter_egg - end if @show - end - - def button - BUTTON(class: 'ui primary button') do - @show ? 'Hide' : 'Show' - end - end - - def input - DIV(class: 'ui input fluid block') do - INPUT(type: :text).on(:change) do |evt| - # we are updating the value per keypress - # using mutate will cause a rerender - mutate @input_value = evt.target.value - end - end - end - - def output - # rerender whenever input_value changes - P { "#{@input_value}" } - end - - def easter_egg - H2 {'you found it!'} if @input_value == 'egg' - end ++ It can take any number of expressions: +```RUBY +mutate @state1 = 'something', @state2 = 'something else' +``` ++ or it can take a block: +```Ruby +mutate do + ... compute the new state ... + @state = ... end ``` -### State and HTTP responses +In both cases the result returned by `mutate` will be the last expression executed. -Often your UI will re-render based on the response to a HTTP request to a remote service. Hyperstack does not need to understand the internals of the HTTP response JSON, but does need to _observe_ the object holding that response so we call `mutate` when updating our response object in the block which executes when the HTTP.get promise resolves. +### The `mutator` Class Method -```ruby -class FaaS < HyperComponent - render(DIV) do - BUTTON { 'faastruby.io' }.on(:click) do - faast_ruby - end +This pattern: - DIV(class: :block) do - P { @hello_response['function_response'].to_s } - P { "executed in #{@hello_response['execution_time']} ms" } - end if @hello_response - end - - def faast_ruby - HTTP.get('https://api.faastruby.io/paulo/hello-world', - data: {time: true} - ) do |response| - # this code executes when the promise resolves - # notice that we call mutate when updating the state instance variable - mutate @hello_response = response.json if response.ok? - end +```RUBY +class SomeComponent < HyperComponent + def update_some_state(some_args) + ... compute new state ... + mutate ... end + ... end ``` +is common enough that Hyperstack provides two ways to shorten this code. The first is the +`mutator` class method: +```Ruby + ... + mutator :update_some_state do |some_args| + ...compute new state ... + end + ... +``` +In other words `mutator` defines a method that is wrapped in a call to `mutate`. It also has +the advantage of clearly declaring that this method will be mutating the components state. -### State and updating interval - -One common use case is a component wanting to update itself on a time interval. It's easy to use the kernel method `every`, but it's important to cancel your interval when you don't need it anymore to save memory. Hyperstack provides Lifecycle Methods \(covered in the next section\) that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide a React friendly `every` function that will automatically get cleaned up when your component is destroyed. - -```ruby -module ReactInterval - - def self.included(base) - base.before_mount do - @intervals = [] - end - - base.before_unmount do - @intervals.each(&:stop) - end - end +### The `state_accessor`, `state_reader` and `state_writer` Methods - def every(seconds, &block) - Kernel.every(seconds, &block).tap { |i| @intervals << i } - end -end +Often all a mutator method will do is assign a new value to a state. For this case Hyperstack provides +the `state_accessor`, `state_reader` and `state_writer` methods, that parallel Ruby's `attribute_accessor`, +`attribute_reader` and `attribute_writer` methods: -class TickTock < HyperComponent - include ReactInterval +```Ruby + state_accessor :some_state + ... + some_state = some_state + 1 # or just some_state += some_state +``` +In otherwords the `state_accessor` creates methods that allow read/write access to the underlying instance variable +including the call to `mutate`. - before_mount do - @seconds = 0 - end +Again the advantage is not only less typing but also clarity of code and intention. - after_mount do - every(1) { mutate @seconds = @seconds + 1 } - end +### Sharing State - render(DIV) do - P { "Hyperstack has been running for #{@seconds} seconds" } - end -end -``` +You can also use and share state at the class level and create "stateful" class libraries. This is described in the **[chapter on HyperState...](/hyper-state.md)** -Notice that TickTock effectively has two `before_mount` methods, one that is called to initialize the `@intervals` array and another to initialize `@seconds` +### The `force_update!` Method +We said above only state mutation can start a rerender. The `force_update!` method is the exception to this rule, as it will +force a component to rerender just because you said so. If you have to use `force_update!` you may be doing something +wrong, so use carefully. From 1ad396c8112a1bf0a860b15f7797c728fb1688d2 Mon Sep 17 00:00:00 2001 From: catmando Date: Thu, 25 Mar 2021 17:45:22 -0400 Subject: [PATCH 216/307] added all the new files --- docs/.bookignore | 1 + docs/client-dsl/error-recovery.md | 84 + docs/client-dsl/events-and-callbacks.md | 173 + docs/client-dsl/methods.md | 4 + docs/client-dsl/params.md | 195 + docs/client-dsl/predefined-events.md | 299 + docs/hyper-router/README.md | 430 + docs/hyper-state/README.md | 357 + docs/specs/.browserslistrc | 1 + docs/specs/.gitignore | 38 + docs/specs/.ruby-version | 1 + docs/specs/Gemfile | 75 + docs/specs/Gemfile.lock | 443 + docs/specs/Gemfile.orig | 72 + docs/specs/Procfile | 2 + docs/specs/README.md | 24 + docs/specs/Rakefile | 6 + docs/specs/app/assets/config/manifest.js | 3 + docs/specs/app/assets/images/.keep | 0 .../app/assets/javascripts/application.js | 17 + docs/specs/app/assets/javascripts/cable.js | 13 + .../app/assets/javascripts/channels/.keep | 0 .../app/assets/stylesheets/application.css | 15 + .../app/channels/application_cable/channel.rb | 4 + .../channels/application_cable/connection.rb | 4 + .../app/controllers/application_controller.rb | 2 + docs/specs/app/controllers/concerns/.keep | 0 docs/specs/app/helpers/application_helper.rb | 2 + .../hyperstack/components/hyper_component.rb | 13 + .../hyperstack/models/application_record.rb | 6 + docs/specs/app/hyperstack/models/sample.rb | 2 + .../images/hyperloop-logo-medium-pink.png | Bin 0 -> 16477 bytes .../app/javascript/packs/client_and_server.js | 21 + .../specs/app/javascript/packs/client_only.js | 6 + docs/specs/app/jobs/application_job.rb | 2 + docs/specs/app/mailers/application_mailer.rb | 4 + docs/specs/app/models/concerns/.keep | 0 docs/specs/app/models/sample.rb | 5 + .../policies/hyperstack/application_policy.rb | 16 + .../app/views/layouts/application.html.erb | 15 + docs/specs/app/views/layouts/mailer.html.erb | 13 + docs/specs/app/views/layouts/mailer.text.erb | 1 + docs/specs/babel.config.js | 70 + docs/specs/bin/bundle | 3 + docs/specs/bin/rails | 9 + docs/specs/bin/rake | 9 + docs/specs/bin/setup | 36 + docs/specs/bin/spring | 17 + docs/specs/bin/update | 31 + docs/specs/bin/webpack | 18 + docs/specs/bin/webpack-dev-server | 18 + docs/specs/bin/yarn | 11 + docs/specs/config.ru | 5 + docs/specs/config/application.rb | 33 + docs/specs/config/boot.rb | 4 + docs/specs/config/cable.yml | 10 + docs/specs/config/credentials.yml.enc | 1 + docs/specs/config/database.yml | 25 + docs/specs/config/environment.rb | 5 + docs/specs/config/environments/development.rb | 61 + docs/specs/config/environments/production.rb | 94 + docs/specs/config/environments/test.rb | 49 + .../application_controller_renderer.rb | 8 + docs/specs/config/initializers/assets.rb | 15 + .../initializers/backtrace_silencers.rb | 7 + .../initializers/content_security_policy.rb | 25 + .../config/initializers/cookies_serializer.rb | 5 + .../initializers/filter_parameter_logging.rb | 4 + docs/specs/config/initializers/hyperstack.rb | 46 + docs/specs/config/initializers/inflections.rb | 16 + docs/specs/config/initializers/mime_types.rb | 4 + .../config/initializers/wrap_parameters.rb | 14 + docs/specs/config/locales/en.yml | 33 + docs/specs/config/master.key | 1 + docs/specs/config/puma.rb | 37 + docs/specs/config/routes.rb | 6 + docs/specs/config/spring.rb | 6 + docs/specs/config/storage.yml | 34 + docs/specs/config/webpack/development.js | 5 + docs/specs/config/webpack/environment.js | 3 + docs/specs/config/webpack/production.js | 5 + docs/specs/config/webpack/test.js | 5 + docs/specs/config/webpacker.yml | 92 + docs/specs/db/development.sqlite3 | Bin 0 -> 36864 bytes .../migrate/20210318185111_create_samples.rb | 10 + docs/specs/db/schema.rb | 22 + docs/specs/db/seeds.rb | 7 + docs/specs/db/test.sqlite3 | Bin 0 -> 28672 bytes docs/specs/lib/assets/.keep | 0 docs/specs/lib/tasks/.keep | 0 docs/specs/log/.keep | 0 docs/specs/log/development.log | 3521 +++++++ docs/specs/log/test.log | 535 ++ docs/specs/package.json | 23 + docs/specs/postcss.config.js | 12 + docs/specs/public/404.html | 67 + docs/specs/public/422.html | 67 + docs/specs/public/500.html | 66 + docs/specs/public/apple-touch-icon.png | 0 docs/specs/public/favicon.ico | 0 docs/specs/public/robots.txt | 1 + docs/specs/spec/client-dsl/README_spec.rb | 39 + .../spec/client-dsl/error-recovery_spec.rb | 135 + .../client-dsl/javascript-components_spec.rb | 103 + docs/specs/spec/spec_helper.rb | 8 + docs/specs/storage/.keep | 0 docs/specs/tmp/.keep | 0 docs/specs/vendor/.keep | 0 docs/specs/yarn.lock | 8087 +++++++++++++++++ 109 files changed, 15952 insertions(+) create mode 100644 docs/.bookignore create mode 100644 docs/client-dsl/error-recovery.md create mode 100644 docs/client-dsl/events-and-callbacks.md create mode 100644 docs/client-dsl/methods.md create mode 100644 docs/client-dsl/params.md create mode 100644 docs/client-dsl/predefined-events.md create mode 100644 docs/hyper-router/README.md create mode 100644 docs/hyper-state/README.md create mode 100644 docs/specs/.browserslistrc create mode 100644 docs/specs/.gitignore create mode 100644 docs/specs/.ruby-version create mode 100644 docs/specs/Gemfile create mode 100644 docs/specs/Gemfile.lock create mode 100644 docs/specs/Gemfile.orig create mode 100644 docs/specs/Procfile create mode 100644 docs/specs/README.md create mode 100644 docs/specs/Rakefile create mode 100644 docs/specs/app/assets/config/manifest.js create mode 100644 docs/specs/app/assets/images/.keep create mode 100644 docs/specs/app/assets/javascripts/application.js create mode 100644 docs/specs/app/assets/javascripts/cable.js create mode 100644 docs/specs/app/assets/javascripts/channels/.keep create mode 100644 docs/specs/app/assets/stylesheets/application.css create mode 100644 docs/specs/app/channels/application_cable/channel.rb create mode 100644 docs/specs/app/channels/application_cable/connection.rb create mode 100644 docs/specs/app/controllers/application_controller.rb create mode 100644 docs/specs/app/controllers/concerns/.keep create mode 100644 docs/specs/app/helpers/application_helper.rb create mode 100644 docs/specs/app/hyperstack/components/hyper_component.rb create mode 100644 docs/specs/app/hyperstack/models/application_record.rb create mode 100644 docs/specs/app/hyperstack/models/sample.rb create mode 100644 docs/specs/app/javascript/images/hyperloop-logo-medium-pink.png create mode 100644 docs/specs/app/javascript/packs/client_and_server.js create mode 100644 docs/specs/app/javascript/packs/client_only.js create mode 100644 docs/specs/app/jobs/application_job.rb create mode 100644 docs/specs/app/mailers/application_mailer.rb create mode 100644 docs/specs/app/models/concerns/.keep create mode 100644 docs/specs/app/models/sample.rb create mode 100644 docs/specs/app/policies/hyperstack/application_policy.rb create mode 100644 docs/specs/app/views/layouts/application.html.erb create mode 100644 docs/specs/app/views/layouts/mailer.html.erb create mode 100644 docs/specs/app/views/layouts/mailer.text.erb create mode 100644 docs/specs/babel.config.js create mode 100755 docs/specs/bin/bundle create mode 100755 docs/specs/bin/rails create mode 100755 docs/specs/bin/rake create mode 100755 docs/specs/bin/setup create mode 100755 docs/specs/bin/spring create mode 100755 docs/specs/bin/update create mode 100755 docs/specs/bin/webpack create mode 100755 docs/specs/bin/webpack-dev-server create mode 100755 docs/specs/bin/yarn create mode 100644 docs/specs/config.ru create mode 100644 docs/specs/config/application.rb create mode 100644 docs/specs/config/boot.rb create mode 100644 docs/specs/config/cable.yml create mode 100644 docs/specs/config/credentials.yml.enc create mode 100644 docs/specs/config/database.yml create mode 100644 docs/specs/config/environment.rb create mode 100644 docs/specs/config/environments/development.rb create mode 100644 docs/specs/config/environments/production.rb create mode 100644 docs/specs/config/environments/test.rb create mode 100644 docs/specs/config/initializers/application_controller_renderer.rb create mode 100644 docs/specs/config/initializers/assets.rb create mode 100644 docs/specs/config/initializers/backtrace_silencers.rb create mode 100644 docs/specs/config/initializers/content_security_policy.rb create mode 100644 docs/specs/config/initializers/cookies_serializer.rb create mode 100644 docs/specs/config/initializers/filter_parameter_logging.rb create mode 100644 docs/specs/config/initializers/hyperstack.rb create mode 100644 docs/specs/config/initializers/inflections.rb create mode 100644 docs/specs/config/initializers/mime_types.rb create mode 100644 docs/specs/config/initializers/wrap_parameters.rb create mode 100644 docs/specs/config/locales/en.yml create mode 100644 docs/specs/config/master.key create mode 100644 docs/specs/config/puma.rb create mode 100644 docs/specs/config/routes.rb create mode 100644 docs/specs/config/spring.rb create mode 100644 docs/specs/config/storage.yml create mode 100644 docs/specs/config/webpack/development.js create mode 100644 docs/specs/config/webpack/environment.js create mode 100644 docs/specs/config/webpack/production.js create mode 100644 docs/specs/config/webpack/test.js create mode 100644 docs/specs/config/webpacker.yml create mode 100644 docs/specs/db/development.sqlite3 create mode 100644 docs/specs/db/migrate/20210318185111_create_samples.rb create mode 100644 docs/specs/db/schema.rb create mode 100644 docs/specs/db/seeds.rb create mode 100644 docs/specs/db/test.sqlite3 create mode 100644 docs/specs/lib/assets/.keep create mode 100644 docs/specs/lib/tasks/.keep create mode 100644 docs/specs/log/.keep create mode 100644 docs/specs/log/development.log create mode 100644 docs/specs/log/test.log create mode 100644 docs/specs/package.json create mode 100644 docs/specs/postcss.config.js create mode 100644 docs/specs/public/404.html create mode 100644 docs/specs/public/422.html create mode 100644 docs/specs/public/500.html create mode 100644 docs/specs/public/apple-touch-icon.png create mode 100644 docs/specs/public/favicon.ico create mode 100644 docs/specs/public/robots.txt create mode 100644 docs/specs/spec/client-dsl/README_spec.rb create mode 100644 docs/specs/spec/client-dsl/error-recovery_spec.rb create mode 100644 docs/specs/spec/client-dsl/javascript-components_spec.rb create mode 100644 docs/specs/spec/spec_helper.rb create mode 100644 docs/specs/storage/.keep create mode 100644 docs/specs/tmp/.keep create mode 100644 docs/specs/vendor/.keep create mode 100644 docs/specs/yarn.lock diff --git a/docs/.bookignore b/docs/.bookignore new file mode 100644 index 000000000..a6de900e3 --- /dev/null +++ b/docs/.bookignore @@ -0,0 +1 @@ +specs/ diff --git a/docs/client-dsl/error-recovery.md b/docs/client-dsl/error-recovery.md new file mode 100644 index 000000000..e16905e56 --- /dev/null +++ b/docs/client-dsl/error-recovery.md @@ -0,0 +1,84 @@ +### When things go wrong... + +The `rescues` lifecycle callbacks run when an error is raised from within or below a component. + +At the end of the rescue the component tree will be completely re-rendered from scratch. In its +simplest form we need nothing but an empty block. + +```ruby +class App < HyperComponent + render(DIV) do + H1 { "Welcome to Our App" } + ContentWhichFailsSometimes() + end + + rescues do + end +end +``` +When an error occurs it will be caught by +the rescues block, and `App` and all lower components will be re-generated (not just re-rendered). + +In most cases you may want to warn the user that something is going wrong, and also record some data +about the event: +```ruby +class App < HyperComponent + render(DIV) do + H1 { "Welcome to Our App" } + if @failure_fall_back + DIV { 'Whoops we had a little problem' } + BUTTON { 'retry' }.on(:click) { mutate @failure_fall_back = false } + else + ContentWhichFailsSometimes() + end + end + + rescues do |err| + @failure_fall_back = true + ReportError.run(err: err) + end +end +``` + +If you don't want to involve the user, then be careful: To prevent infinite loops the React engine +will not rescue failures occurring during the re-generation of the component tree. If not involving +the user you may want to consider how to insure that system state is completely reset in the rescue. + +The rescues method can also take explicit Error types to be rescued: + +```ruby + rescues StandardError, MyInternalError do |err| + ... + end +``` + +Like other lifecycle methods you can have multiple rescues in the same component: + +```ruby + rescues StandardError do |err| + # code for handling StandardError + end + rescues MyInternalError do |err| + # different code for handling MyInternalError + end +``` + +Like Ruby's rescue keyword, errors will be caught by the innermost component with a `rescues` callback that +handles that error. + +The data passed to the rescue handler is an array of two items, the Ruby error that was thrown, and details generated by the React engine. + +```ruby + rescues do |e, info| + # e is a Ruby error, and responds to backtrace, message, etc + # info is a hash currently with the single key :componentStack + # which is string representing the backtrace through the component + # hierarchy + end +``` + +## Caveats + +1. You cannot rescue errors raised in lifecycle handlers in the same component. Errors raised by lifecycle handlers in inner components are fine, just not +in the same component as the rescue. +2. Errors raised in event handlers will neither stop the rendering cycle, nor will they be caught by a rescue callback. diff --git a/docs/client-dsl/events-and-callbacks.md b/docs/client-dsl/events-and-callbacks.md new file mode 100644 index 000000000..a519a250d --- /dev/null +++ b/docs/client-dsl/events-and-callbacks.md @@ -0,0 +1,173 @@ +Params pass data downwards from owner to owned-by component. Data comes back upwards asynchronously +via *callbacks*, which are simply *Procs* passed as params into the owned-by component. + +> **[More on Ruby Procs here ...](notes.md#ruby-procs)** + +The upwards flow of data via callbacks is triggered by some event such as a mouse click, or input change: + +```ruby +class ClickDemo2 < HyperComponent + render do + BUTTON { "click" }.on(:click) { |evt| puts "I was clicked"} + end +end +``` + +When the `BUTTON` is clicked, the event (evt) is passed to the attached click handler. + +The details of the event object will be discussed below. + +### Firing Events from Components + +You can also define events in your components to communicate back to the owner: + +```ruby +class Clicker < HyperComponent + param title: "click" + fires :clicked + before_mount { @clicks = 0 } + render do + BUTTON { title }.on(:click) { clicked!(@clicks += 1) } + end +end + +class ClickDemo3 < HyperComponent + render(DIV) do + DIV { "I have been clicked #{pluralize(@clicks, 'times')}" } if @clicks + Clicker().on(:clicked) { |clicks| mutate @clicks = clicks } + end +end +``` + +Each time the `Clicker's` button is clicked it *fires* the clicked event, indicated +by the event name followed by a bang (!). + +The `clicked` event is received by `ClickDemo3`, and it updates its state. As you +can see events can send arbitrary data back out. + +> Notice also that Clicker does not call mutate. It could, but since the change in +`@clicks` is not used anywhere to control its display there is no need for Clicker +to mutate. + +### Relationship between Events and Params + +Notice how events (and callbacks in general as we will see) move data upwards, while +params move data downwards. We can emphasize this by updating our example: + +```ruby +class ClickDemo4 < HyperComponent + def title + @clicks ? "Click me again!" : "Let's start clicking!" + end + + render(DIV) do + DIV { "I have been clicked #{pluralize(@clicks, 'times')}" } if @clicks + Clicker(title: title).on(:clicked) { |clicks| mutate @clicks = clicks } + end +end +``` + +When `ClickDemo4` is first rendered, the `title` method will return "Let's start clicking!", and +will be passed to `Clicker`. + +The user will (hopefully so we can get on with this chapter) click the button, which will +fire the event. The handler in `ClickDemo4` will mutate its state, causing title to change +to "Click me again!". The new value of the title param will be passed to `Clicker`, and `Clicker` +will re-render with the new title. + +**Events (and callbacks) push data up, params move data down.** + +### Callbacks and Proc Params + +Under the hood Events are simply params of type `Proc`, with the `on` and `fires` method +using some naming conventions to clean things up: + +```ruby +class IntemittentButton < HyperComponent + param :frequency + param :pulse, type: Proc + before_mount { @clicks = 0 } + render do + BUTTON( + on_click: lambda {} do + @clicks += 1 + pulse(@clicks) if (@clicks % frequency).zero? + end + ) { 'click me' } + end +end + +class ClickDemo5 < HyperComponent + render do + IntermittentButton( + frequency: 5, + pulse: -> (total_clicks) { alert "you are clicking a lot" } + ) + end +end +``` + +There is really no reason not to use the `fires` method to declare Proc params, and +no reason not use the `on` method to attach handlers. Both will keep your code clean and tidy. + +### Naming Conventions + +The notation `on(:click)` is short for passing a proc to a param named `on_click`. In general `on(:xxx)` will pass the +given block as the `on_xxx` parameter in a Hyperstack component and `onXxx` in a JS component. + +All the built-in events and many React libraries follow the `on_xxx` (or `onXxx` in JS.) However even if a library does not use +this convention you can still attach the handler using `on('')`. Whatever string is inside the `<..>` brackets will +be used as the param name. + +Likewise the `fires` method is shorthand for creating a `Proc` param following the `on_xxx` naming convention: + +`fires :foo` is short for +`param :foo, type: Proc, alias: :foo!` + +### The `Event` Object + +UI events like `click` send an object of class `Event` to the handler. Some of the data you can get from `Event` objects are: + ++ `target` : the DOM object that was the target of the UI interaction ++ `target.value` : the value of the DOM object ++ `key_code` : the key pressed (for key_down and key_up events) + +> **[Refer to the Predefined Events section for complete details...](predefined-events.md)** + +### Other Sources of Events + +Besides the UI there are several other sources of events: + ++ Timers ++ HTTP Requests ++ Hyperstack Operations ++ Websockets ++ Web Workers + +The way you receive events from these sources depends on the event. Typically though the method will either take a block, or callback proc, or in many cases will return a Promise. +Regardless the event handler will do one of three things: mutate some state within the component, fire an event to a higher level component, or update some shared store. + +> For details on updating shared stores, which is often the best answer **[see the chapter on HyperState...](/hyper-state.md)** + +You have seen the `every` method used to create events throughout this chapter, here is an example with an HTTP post (which returns a promise.) + +```ruby +class SaveButton < HyperComponent + fires :saved + fires :failed + render do + BUTTON { "Save" } + .on(:click) do + # Posting to some non-hyperstack endpoint for example + # Data is our class holding some data + Hyperstack::HTTP.post( + END_POINT, payload: Data.to_payload + ).then do |response| + saved!(response.json) + end.fail do |response| + failed!(response.json) + end + end + end +end +``` diff --git a/docs/client-dsl/methods.md b/docs/client-dsl/methods.md new file mode 100644 index 000000000..0efcd8dab --- /dev/null +++ b/docs/client-dsl/methods.md @@ -0,0 +1,4 @@ +as_node +force_update +after +every diff --git a/docs/client-dsl/params.md b/docs/client-dsl/params.md new file mode 100644 index 000000000..61a5b21ce --- /dev/null +++ b/docs/client-dsl/params.md @@ -0,0 +1,195 @@ +The `param` class method gives _read-only_ access to each of the params passed to the component. Params are accessed as instance methods of the component. +*In React params are called props, but Hyperstack use the more common Rails term `param`.* + +Within a component class the `param` method is used to define the parameter signature of the component. You can think of params as the values that would normally be sent to the instance's `initialize` method, but with the difference that a component will get new parameters during its lifecycle. + +The param declaration has several options providing a default value, expected type, and an internal alias name. + +Examples: + +```ruby +param :foo # declares that we must be provided with a parameter foo when the component is instantiated or re-rerendered. +param :foo => "some default" # declares that foo is optional, and if not present the value "some default" will be used. +param foo: "some default" # same as above using ruby 1.9 JSON style syntax +param :foo, default: "some default" # same as above but uses explicit default key +param :foo, type: String # foo is required and must be of type String +param :foo, type: [String] # foo is required and must be an array of Strings +param foo: [], type: [String] # foo must be an array of strings, and has a default value of the empty array. +param :foo, alias: :something # the alias name will be used for the param (instead of foo) +``` + +#### Accessing param values + +Params are accessible in the component as instance methods. For example: + +```ruby +class Hello < HyperComponent + # visitor has a default value (so its not required) + # and must be of type (i.e. instance of) String + param visitor: "World", type: String + + render do + "Hello #{visitor}" + end +end +``` + +### Param Validation + +As your app grows it's helpful to ensure that your components are used correctly.You do this by specifying the expected ruby class of your parameters. When an invalid value is provided for a param, a warning will be shown in the JavaScript console. Note that for performance reasons type checking is only done in development mode. Here is an example showing typical type specifications: + +```ruby +class ManyParams < HyperComponent + param :an_array, type: [] # or type: Array + param :a_string, type: String + param :array_of_strings, type: [String] + param :a_hash, type: Hash + param :some_class, type: SomeClass # works with any class + param :a_string_or_nil, type: String, allow_nil: true +end +``` + +Note that if the param has a type but can also be nil, add `allow_nil: true` to the specification. + +### Default Param Values + +You can define default values for your `params`: + +```ruby +class ManyParams < HyperComponent + param :an_optional_param, default: "hello", type: String, allow_nil: true +``` + +If no value is provided for `:an_optional_param` it will be given the value `"hello"`, it may also be given the value `nil`. + +Defaults can be provided by the `default` key or using the syntax `param foo: 12` which would default `foo` to 12. + +### Named Child Components as Params + +You can pass a child component as a `param` and then render it in the receiving component. + +```ruby +# in the parent Component... +button = MyButton() +ButtonBar(button: button) + +class ButtonBar < HyperComponent + param :button + + render do + DIV(class: 'button-bar') { button.render } + end +end +``` + +`render` is used to render the child components. **[For details ...](component-details.md#rendering-children)** + +> Notice that this is just a way to pass a child to a component but instead of sending it the "block" with other children you are passing it as a named child. + +### Other Params + +A common type of component is one that extends a basic HTML element in a simple way. Often you'll want to copy any HTML attributes passed to your component to the underlying HTML element. + +To do this use the `others` method which will gather all the params you did not declare into a hash. Then you can pass this hash on to the child component + +```ruby +class CheckLink < HyperComponent + others :attributes + render do + # we just pass along any incoming attributes + A(attributes) { '√ '.span; children.each &:render } + end +end + + # elsewhere + CheckLink(href: "/checked.html") +``` + +Note: `others` builds a hash, so you can merge other data in or even delete elements out as needed. + +### Aliasing Param Names + +Sometimes we can make our component code more readable by using a different param name inside the component than the owner component will use. + +```ruby +class Hello < HyperComponent + param :name + param include_time: true, alias: :time? + render { SPAN { "Hello #{name}#{'the time is '+Time.now if time?}" } } +end +``` + +This way we can keep the interface very clear, but keep our component code short and sweet. + +### Updating Params + +Each time a component is rendered any of the components it owns may be re-rendered as well **but only if any of the params will change in value.** +If none of the params change in value, then the owned-by component will not be rerendered as no parameters have changed. + +Hyperstack determines if a param has changed through a simple Ruby equality check. If `old_params == new_params` then no update is needed. + +For strings, numbers and other scalar values equality is straight forward. Two hashes are equal if they each contain the same number of keys +and if each key-value pair is equal to the corresponding elements in the other hash. Two arrays are equal if they contain the same number of +elements and if each element is equal to the corresponding element in the other array. + +For other objects unless the object defines its own equality method the objects are equal only if they are the same instance. + +Also keep in mind that if you pass an array or hash (or any other non-scalar object) you are passing a reference to the object **not a copy**. + +Lets look at a simple (but contrived) example and see how this all plays out: + +```RUBY +class App < HyperComponent + render do + DIV do + BUTTON { "update" }.on(:click) { force_update! } + new_hash = {foo: {bar: [12, 13]}} + Comp2(param: new_hash) + end + end +end + +class Comp2 < HyperComponent + param :param + render do + DIV { param } + end +end +``` + +Even though we have not gotten to event handlers yet, you can see what is going on: When we click the update button we call `force_update!` +which will force the `App` component to rerender. +> By the way `force_update!` is almost never used, but we are using it here +just to make the example clear.) + +Will `Comp2` rerender? No - because even though we are creating a new hash, the old hash and new hash are equal in value. + +What if we change the hash to be `{foo: {bar: [12, Time.now]}}`. Will `Comp2` re-render now? Yes because the old and new hashes are no longer equal. + +What if we changed `App` like this: + +```RUBY +class App < HyperComponent + # initialize an instance variable before rendering App + before_mount { @hash = {foo: {bar: [12, 13]}} } + render do + DIV do + BUTTON { "update" }.on(:click) do + @hash[:foo][:bar][2] = Time.now + force_update! + end + Comp2(param: @hash) + end + end +end +``` + +Will `Comp2` still update? No. `Comp2` received the value of `@hash` on the first render, and so `Comp2` is rendering the same copy of `@hash` that `App` is changing. +So when we compare *old* verses *new* we are comparing the same object, so the values are equal even though the contents of the hash has changed. + +### Conclusion + +That does not seem like a very happy ending, but the case we used was not very realistic. If you stick to passing simple scalars, or hashes and arrays +whose values don't change after they have been passed, things will work fine. And for situations where you do need to store and +manipulate complex data, you can use the **[the Hyperstack::Observable module](/hyper-state/README.md)** to build safe classes that don't +have the problems seen above. diff --git a/docs/client-dsl/predefined-events.md b/docs/client-dsl/predefined-events.md new file mode 100644 index 000000000..d1f3a1489 --- /dev/null +++ b/docs/client-dsl/predefined-events.md @@ -0,0 +1,299 @@ +# Event Handlers + +Event Handlers are attached to tags and components using the `on` method. + +```ruby +SELECT ... do + ... +end.on(:change) do |e| + mutate @mode = e.target.value.to_i +end +``` + +The `on` method takes the event name symbol \(note that `onClick` becomes `:click`\) and the block is passed the React.js event object. + +```ruby +BUTTON { 'Press me' }.on(:click) { do_something } +# you can add an event handler to any HTML element +H1(class: :cursor_hand) { 'Click me' }.on(:click) { do_something } +``` + +Event handlers can be chained like so + +```ruby +INPUT ... do + ... + end.on(:key_up) do |e| + ... + end.on(:change) do |e| + ... +end +``` + +### Event Handling and Synthetic Events + +With React you attach event handlers to elements using the `on` method. React ensures that all events behave identically in IE8 and above by implementing a synthetic event system. That is, React knows how to bubble and capture events according to the spec, and the events passed to your event handler are guaranteed to be consistent with [the W3C spec](http://www.w3.org/TR/DOM-Level-3-Events/), regardless of which browser you're using. + +### Under the Hood: Event Delegation + +React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see [David Walsh's excellent blog post](http://davidwalsh.name/event-delegate). + +### React::Event + +Your event handlers will be passed instances of `React::Event`, a wrapper around react.js's `SyntheticEvent` which in turn is a cross browser wrapper around the browser's native event. It has the same interface as the browser's native event, including `stopPropagation()` and `preventDefault()`, except the events work identically across all browsers. + +For example: + +```ruby +class YouSaid < HyperComponent + + render(DIV) do + INPUT(value: state.value). + on(:key_down) do |e| + alert "You said: #{state.value}" if e.key_code == 13 + end. + on(:change) do |e| + @mutate value = e.target.value + end + end +end +``` + +If you find that you need the underlying browser event for some reason use the `native_event`. + +In the following responses shown as \(native ...\) indicate the value returned is a native object with an Opal wrapper. In some cases there will be opal methods available \(i.e. for native DOMNode values\) and in other cases you will have to convert to the native value with `.to_n` and then use javascript directly. + +Every `React::Event` has the following methods: + +```ruby +bubbles -> Boolean +cancelable -> Boolean +current_target -> (native DOM node) +default_prevented -> Boolean +event_phase -> Integer +is_trusted -> Boolean +native_event -> (native Event) +prevent_default -> Proc +is_default_prevented -> Boolean +stop_propagation -> Proc +is_propagation_stopped -> Boolean +target -> (native DOMEventTarget) +timestamp -> Integer (use Time.at to convert to Time) +type -> String +``` + +### Event pooling + +The underlying React `SyntheticEvent` is pooled. This means that the `SyntheticEvent` object will be reused and all properties will be nullified after the event method has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way. + +### Supported Events + +React normalizes events so that they have consistent properties across different browsers. + +### Clipboard Events + +Event names: + +```ruby +:copy, :cut, :paste +``` + +Available Methods: + +```ruby +clipboard_data -> (native DOMDataTransfer) +``` + +### Composition Events \(not tested\) + +Event names: + +```ruby +:composition_end, :composition_start, :composition_update +``` + +Available Methods: + +```ruby +data -> String +``` + +### Keyboard Events + +Event names: + +```ruby +:key_down, :key_press, :key_up, :enter +``` + +> The `enter` event is fired on key_down where key_code == 13 (the enter key) + +Available Methods: + +```ruby +alt_key -> Boolean +char_code -> Integer +ctrl_key -> Boolean +get_modifier_state(key) -> Boolean (i.e. get_modifier_key(:Shift) +key -> String +key_code -> Integer +locale -> String +location -> Integer +meta_key -> Boolean +repeat -> Boolean +shift_key -> Boolean +which -> Integer +``` + +### Focus Events + +Event names: + +```ruby +:focus, :blur +``` + +Available Methods: + +```ruby +related_target -> (Native DOMEventTarget) +``` + +These focus events work on all elements in the React DOM, not just form elements. + +### Form Events + +Event names: + +```ruby +:change, :input, :submit +``` + +### Mouse Events + +Event names: + +```ruby +:click, :context_menu, :double_click, :drag, :drag_end, :drag_enter, :drag_exit +:drag_leave, :drag_over, :drag_start, :drop, :mouse_down, :mouse_enter, +:mouse_leave, :mouse_move, :mouse_out, :mouse_over, :mouse_up +``` + +The `:mouse_enter` and `:mouse_leave` events propagate from the element being left to the one being entered instead of ordinary bubbling and do not have a capture phase. + +Available Methods: + +```ruby +alt_key -> Boolean +button -> Integer +buttons -> Integer +client_x -> Integer +number client_y -> Integer +ctrl_key -> Boolean +get_modifier_state(key) -> Boolean +meta_key -> Boolean +page_x -> Integer +page_y -> Integer +related_target -> (Native DOMEventTarget) +screen_x -> Integer +screen_y -> Integer +shift_key -> Boolean +``` + +### Drag and Drop example + +Here is a Hyperstack version of this [w3schools.com](https://www.w3schools.com/html/html5_draganddrop.asp) example: + +```ruby +DIV(id: "div1", style: {width: 350, height: 70, padding: 10, border: '1px solid #aaaaaa'}) + .on(:drop) do |ev| + ev.prevent_default + data = `#{ev.native_event}.native.dataTransfer.getData("text")` + `#{ev.target}.native.appendChild(document.getElementById(data))` + end + .on(:drag_over) { |ev| ev.prevent_default } + +IMG(id: "drag1", src: "https://www.w3schools.com/html/img_logo.gif", draggable: "true", width: 336, height: 69) + .on(:drag_start) do |ev| + `#{ev.native_event}.native.dataTransfer.setData("text", #{ev.target}.native.id)` + end +``` + +### Selection events + +Event names: + +```ruby +onSelect +``` + +### Touch events + +Event names: + +```ruby +:touch_cancel, :touch_end, :touch_move, :touch_start +``` + +Available Methods: + +```ruby +alt_key -> Boolean +changed_touches -> (Native DOMTouchList) +ctrl_key -> Boolean +get_modifier_state(key) -> Boolean +meta_key -> Boolean +shift_key -> Boolean +target_touches -> (Native DOMTouchList) +touches -> (Native DomTouchList) +``` + +### UI Events + +Event names: + +```ruby +:scroll +``` + +Available Methods: + +```ruby +detail -> Integer +view -> (Native DOMAbstractView) +``` + +### Wheel Events + +Event names: + +```ruby +wheel +``` + +Available Methods: + +```ruby +delta_mode -> Integer +delta_x -> Integer +delta_y -> Integer +delta_z -> Integer +``` + +### Media Events + +Event names: + +```ruby +:abort, :can_play, :can_play_through, :duration_change,:emptied, :encrypted, :ended, :error, :loaded_data, +:loaded_metadata, :load_start, :pause, :play, :playing, :progress, :rate_change, :seeked, :seeking, :stalled, +:on_suspend, :time_update, :volume_change, :waiting +``` + +### Image Events + +Event names: + +```ruby +:load, :error +``` diff --git a/docs/hyper-router/README.md b/docs/hyper-router/README.md new file mode 100644 index 000000000..96dd377f6 --- /dev/null +++ b/docs/hyper-router/README.md @@ -0,0 +1,430 @@ +# Client-side Routing + +HyperRouter is a DSL wrapper for [ReactRouter v4.x](https://github.com/ReactTraining/react-router) to provide client-side routing for Single Page Applications (SPA). + +## Usage + +```ruby +class AppRouter + include Hyperstack::Component + include Hyperstack::Router::Helpers + include Hyperstack::Router + + render(DIV) do + UL do + LI { Link('/') { 'Home' } } + LI { Link('/about') { 'About' } } + end + Route('/', exact: true, mounts: Home) + Route('/about', mounts: About) + end +end + +class Home + include Hyperstack::Component + render(DIV) do + H2 { 'Home' } + end +end +``` + +## DSL + +### Router + +This is the Router module which you include in your top level component: + +```ruby +class MyRouter + include Hyperstack::Component + include Hyperstack::Router +end +``` + +With the base Router class, you can also specify the history you want to use. + +This can be done either using a macro: + +```ruby +class MyRouter + include Hyperstack::Component + include Hyperstack::Router + + history :browser # this is the default option if no other is specified +end +``` + +The macro accepts three options: `:browser`, `:hash`, or `:memory`. + +Or by defining the `history` method: + +```ruby +class MyRouter + include Hyperstack::Component + include Hyperstack::Router + + def history + self.class.browser_history + end +end +``` + +### Rendering a Router + +Use the `render` macro as normal. Note you cannot redefine the `render` instance method in a Router componenent + +```ruby +class MyRouter + ... + + render(DIV) do + H1 { 'Hello world!' } + end +end +``` + +### Routes + +Routes are defined with special pseudo components you call inside the router/components. The router determines which of the routes to actually mount based on the current URL. + +```ruby +class MyRouter + ... + + render(DIV) do + Route('/', mounts: HelloWorld) + end +end + +class HelloWorld + render do + H1 { 'Hello world!' } + end +end +``` + +The `Route` method takes a url path, and these options: + +* `mounts: Component` The component you want to mount when routed to +* `exact: Boolean` When true, the path must match the location exactly +* `strict: Boolean` When true, the path will only match if the location and path **both** have/don't have a trailing slash + +The `Route` method can also take a block instead of the `mounts` option. + +```ruby +class MyRouter + ... + + render(DIV) do + Route('/', exact: true) do + H1 { 'Hello world!' } + end + end +end +``` + +The block will be given the match, location, and history data: + +```ruby +class MyRouter + ... + + render(DIV) do + Route('/:name') do |match, location, history| + H1 { "Hello #{match.params[:name]} from #{location.pathname}, click me to go back!" } + .on(:click) { history.go_back } + end + end +end +``` + +* The `Hyperstack::Router::Helpers` is useful for components mounted by the router. +* This automatically sets the `match`, `location`, and `history` params, + + and also gives you instance methods with those names. + +* You can use either `params.match` or just `match`. + + and gives you access to the `Route` method and more. + +* This allows you to create inner routes as you need them. + +```ruby +class MyRouter + include Hyperstack::Component + include Hyperstack::Router::Helpers + include Hyperstack::Router + + render(DIV) do + Route('/:name', mounts: Greet) + end +end + +class Greet + include Hyperstack::Component + include Hyperstack::Router::Helpers + + render(DIV) do + H1 { "Hello #{match.params[:foo]}!" } + Route(match.url, exact: true) do + H2 { 'What would you like to do?' } + end + Route("#{match.url}/:activity", mounts: Activity) + end +end + +class Activity + include Hyperstack::Component + include Hyperstack::Router::Helpers + include Hyperstack::Router + + render(DIV) do + H2 { params.match.params[:activity] } + end +end +``` + +Normally routes will **always** render alongside sibling routes that match as well. + +```ruby +class MyRouter + ... + + render(DIV) do + Route('/goodbye', mounts: Goodbye) + Route('/:name', mounts: Greet) + end +end +``` + +### Switch + +Going to `/goodbye` would match `/:name` as well and render `Greet` with the `name` param with the value 'goodbye'. To avoid this behavior and only render one matching route at a time, use a `Switch` component. + +```ruby +class MyRouter + ... + + render(DIV) do + Switch do + Route('/goodbye', mounts: Goodbye) + Route('/:name', mounts: Greet) + end + end +end +``` + +Now, going to `/goodbye` would match the `Goodbye` route first and only render that component. + +### Links + +Links are provided by both the `Hyperstack::Router` and `Hyperstack::Router::Helper` modules. + +The `Link` method takes a url path, and these options: + +* `search: String` adds the specified string to the search query +* `hash: String` adds the specified string to the hash location + + it can also take a block of children to render inside it. + +```ruby +class MyRouter + ... + + render(DIV) do + Link('/Gregor Clegane') + + Route('/', exact: true) { H1() } + Route('/:name') do |match| + H1 { "Will #{match.params[:name]} eat all the chickens?" } + end + end +end +``` + +### NavLinks + +NavLinks are the same as Links, but will add styling attributes when it matches the current url + +* `active_class: String` adds the class to the link when the url matches +* `active_style: String` adds the style to the link when the url matches +* `active: Proc` A proc that will add extra logic to determine if the link is active + +```ruby +class MyRouter + ... + + render(DIV) do + NavLink('/Gregor Clegane', active_class: 'active-link') + NavLink('/Rodrik Cassel', active_style: { color: 'grey' }) + NavLink('/Oberyn Martell', + active: ->(match, location) { + match && match.params[:name] && match.params[:name] =~ /Martell/ + }) + + Route('/', exact: true) { H1() } + Route('/:name') do |match| + H1 { "Will #{match.params[:name]} eat all the chickens?" } + end + end +end +``` + +### Pre-rendering + +Pre-rendering is automatically taken care for you under the hood. + +## Setup + +To setup HyperRouter: + +* Install the gem +* Your page should render your router as its top-level-component \(first component to be rendered on the page\) - in the example below this would be `AppRouter` +* You will need to configure your server to route all unknown routes to the client-side router \(Rails example below\) + +### With Rails + +Assuming your router is called `AppRouter`, add the following to your `routes.rb` + +```ruby +root 'Hyperstack#AppRouter' # see note below +match '*all', to: 'Hyperstack#AppRouter', via: [:get] # this should be the last line of routes.rb +``` + +Note: + +`root 'Hyperstack#AppRouter'` is shorthand which will automagically create a Controller, View and launch `AppRouter` as the top-level Component. If you are rendering your Component via your own COntroller or View then ignore this line. + +### Example + +Here is the basic JSX example that is used on the [react-router site](https://reacttraining.com/react-router/) + +```jsx +import React from 'react' +import { + BrowserRouter as Router, + Route, + Link +} from 'react-router-dom' + +const BasicExample = () => ( + +
    +
      +
    • Home
    • +
    • About
    • +
    • Topics
    • +
    + +
    + + + + +
    +
    +) + +const Home = () => ( +
    +

    Home

    +
    +) + +const About = () => ( +
    +

    About

    +
    +) + +const Topics = ({ match }) => ( +
    +

    Topics

    +
      +
    • Rendering with React
    • +
    • Components
    • +
    • Props v. State
    • +
    + + + ( +

    Please select a topic.

    + )}/> +
    +) + +const Topic = ({ match }) => ( +
    +

    {match.params.topicId}

    +
    +) + +export default BasicExample +``` + +And here is the same example in Hyperstack: + +```ruby +class BasicExample + include Hyperstack::Component + include Hyperstack::Router::Helpers + include Hyperstack::Router + + render(DIV) do + UL do + LI { Link('/') { 'Home' } } + LI { Link('/about') { 'About' } } + LI { Link('/topics') { 'Topics' } } + end + + Route('/', exact: true, mounts: Home) + Route('/about', mounts: About) + Route('/topics', mounts: Topics) + end +end + +class Home + include Hyperstack::Component + include Hyperstack::Router::Helpers + + render(DIV) do + H2 { 'Home' } + end +end + +class About + include Hyperstack::Component + include Hyperstack::Router::Helpers + + render(:div) do + H2 { 'About' } + end +end + +class Topics + include Hyperstack::Component + include Hyperstack::Router::Helpers + + render(DIV) do + H2 { 'Topics' } + UL() do + LI { Link("#{match.url}/rendering") { 'Rendering with React' } } + LI { Link("#{match.url}/components") { 'Components' } } + LI { Link("#{match.url}/props-v-state") { 'Props v. State' } } + end + Route("#{match.url}/:topic_id", mounts: Topic) + Route(match.url, exact: true) do + H3 { 'Please select a topic.' } + end + end +end + +class Topic + include Hyperstack::Component + include Hyperstack::Router::Helpers + + render(:div) do + H3 { match.params[:topic_id] } + end +end +``` diff --git a/docs/hyper-state/README.md b/docs/hyper-state/README.md new file mode 100644 index 000000000..97e96b52b --- /dev/null +++ b/docs/hyper-state/README.md @@ -0,0 +1,357 @@ + +### Param Equality + +Params can be arbitrarily + +### Params, Immutability and State + +Some care must be taken when passing params that are not simple scalars (string, numbers, etc) or JSON like +combinations of arrays and hashes. When passing more complex objects + +Hyperstack differs from React in how it deals with changes in param values + +A component will re-render when new param values are received. If it appears that parameter values have changed, +then the component will not re-render. For scalars such as strings and numbers and JSON like combinations of arrays +and hashes, the component will be re-rendered if the value of the param changes. + +For more complex objects such as application defined classes, there is generally no way to easily determine that an +object's value has changed. Hyperstack solves this problem with the `Observable` class, that allows objects to +track which components are depending on their values + +In React \(and Hyperstack\) state is mutable. Changes \(mutations\) to state variables cause Components to re-render. Where state is passed into a child Component as a `param`, it will cause a re-rendering of that child Component. Change flows from a parent to a child - change does not flow upward and this is why params are not mutable. + +State variables are normal instance variables or objects. When a state variable changes, we use the `mutate` method to get React's attention and cause a re-render. Like normal instance variables, state variables are created when they are first accessed, so there is no explicit declaration. + +The syntax of `mutate` is simple - its `mutate` and any other number of parameters and/or a block. Normal evaluation means the parameters are going to be evaluated first, and then `mutate` gets called. + +* `mutate @foo = 12, @bar[:zap] = 777` executes the two assignments first, then calls mutate +* or you can say `mutate { @foo = 12; @bar[:zap] = 777 }` which is more explicit, and does the same thing + +Here are some examples: + +```ruby +class Counter < HyperComponent + before_mount do + @count = 0 # optional initialization + end + + render(DIV) do + # note how we mutate count + BUTTON { "+" }.on(:click) { mutate @count += 1) } + P { @count.to_s } + end +end +``` + +```ruby +class LikeButton < HyperComponent + render(DIV) do + BUTTON do + "You #{@liked ? 'like' : 'haven\'t liked'} this. Click to toggle." + end.on(:click) do + mutate @liked = !@liked + end + end +end +``` + +### Components are Just State Machines + +React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent. + +In React, you simply update a component's state, and then the new UI will be rendered on this new state. React takes care of updating the DOM for you in the most efficient way. + +### What Components Should Have State? + +Most of your components should simply take some params and render based on their value. However, sometimes you need to respond to user input, a server request or the passage of time. For this you use state. + +**Try to keep as many of your components as possible stateless.** By doing this you'll isolate the state to its most logical place and minimize redundancy, making it easier to reason about your application. + +A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via `param`s. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way. + +State can be held in any object \(not just a Component\). For example: + +```ruby +class TestIt + def self.swap_state + @@test = !@@test + end + + def self.result + @@test ? 'pass' : 'fail' + end +end + +class TestResults < HyperComponent + render(DIV) do + P { "Test is #{TestIt.result}" } + BUTTON { 'Swap' }.on(:click) do + mutate TestIt::swap_state + end + end +end +``` + +In the example above, the singleton class `TestIt` holds its own internal state which is changed through a `swap_state` class method. The `TestResults` Component has no knowledge of the internal workings of the `TestIt` class. + +When the BUTTON is pressed, we call `mutate`, passing the object which is being mutated. The actual mutated value is not important, it is the fact that the _observed_ object \(our `TestIt` class\) is being mutated that will cause a re-render of the _observing_ `TestResults` Component. Think about `mutate` as a way of telling React that the Component needs to be re-rendered as the state has changed. + +In the example above, we could also move the _observing_ and _mutating_ behaviour out of the Component completely and manage it in the `TestIt` class - in this case, we would call it a Store. Stores are covered in the Hyper-Store documentation later. + +### What Should Go in State? + +**State should contain data that a component's instance variables, event handlers, timers, or http requests may change and trigger a UI update.** + +When building a stateful component, think about the minimal possible representation of its state, and only store those properties in `state`. Add to your class methods to compute higher level values from your state variables. Avoid adding redundant or computed values as state variables as these values must then be kept in sync whenever state changes. + +### What Shouldn't Go in State? + +State should contain the minimal amount of data needed to represent your UI's state. As such, it should not contain: + +* **Computed data:** Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation during rendering. For example, if you have an array of list items in state and you want to render the count as a string, simply render `"#{@list_items.length} list items'` in your `render` method rather than storing the count as another state. +* **Data that does not effect rendering:** Changing an instance variable \(or any object\) that does not affect rendering does not need to be mutated \(i.e you do not need to call `mutate`\). + +The rule is simple: anytime you are updating a state variable use `mutate` and your UI will be re-rendered appropriately. + +### State and user input + +Often in a UI you gather input from a user and re-render the Component as they type. For example: + +```ruby +class UsingState < HyperComponent + + render(DIV) do + # the button method returns an HTML element + # .on(:click) is an event handeler + # notice how we use the mutate method to get + # React's attention. This will cause a + # re-render of the Component + button.on(:click) { mutate(@show = !@show) } + DIV do + input + output + easter_egg + end if @show + end + + def button + BUTTON(class: 'ui primary button') do + @show ? 'Hide' : 'Show' + end + end + + def input + DIV(class: 'ui input fluid block') do + INPUT(type: :text).on(:change) do |evt| + # we are updating the value per keypress + # using mutate will cause a rerender + mutate @input_value = evt.target.value + end + end + end + + def output + # rerender whenever input_value changes + P { "#{@input_value}" } + end + + def easter_egg + H2 {'you found it!'} if @input_value == 'egg' + end +end +``` + +### State and HTTP responses + +Often your UI will re-render based on the response to a HTTP request to a remote service. Hyperstack does not need to understand the internals of the HTTP response JSON, but does need to _observe_ the object holding that response so we call `mutate` when updating our response object in the block which executes when the HTTP.get promise resolves. + +```ruby +class FaaS < HyperComponent + render(DIV) do + BUTTON { 'faastruby.io' }.on(:click) do + faast_ruby + end + + DIV(class: :block) do + P { @hello_response['function_response'].to_s } + P { "executed in #{@hello_response['execution_time']} ms" } + end if @hello_response + end + + def faast_ruby + HTTP.get('https://api.faastruby.io/paulo/hello-world', + data: {time: true} + ) do |response| + # this code executes when the promise resolves + # notice that we call mutate when updating the state instance variable + mutate @hello_response = response.json if response.ok? + end + end +end +``` + +### State and updating interval + +One common use case is a component wanting to update itself on a time interval. It's easy to use the kernel method `every`, but it's important to cancel your interval when you don't need it anymore to save memory. Hyperstack provides Lifecycle Methods \(covered in the next section\) that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide a React friendly `every` function that will automatically get cleaned up when your component is destroyed. + +```ruby +module ReactInterval + + def self.included(base) + base.before_mount do + @intervals = [] + end + + base.before_unmount do + @intervals.each(&:stop) + end + end + + def every(seconds, &block) + Kernel.every(seconds, &block).tap { |i| @intervals << i } + end +end + +class TickTock < HyperComponent + include ReactInterval + + before_mount do + @seconds = 0 + end + + after_mount do + every(1) { mutate @seconds = @seconds + 1 } + end + + render(DIV) do + P { "Hyperstack has been running for #{@seconds} seconds" } + end +end +``` + +Notice that TickTock effectively has two `before_mount` methods, one that is called to initialize the `@intervals` array and another to initialize `@seconds` + + + +# Stores + +A core concept behind React is that Components contain their own state and pass state down to their children as params. React re-renders the interface based on those state changes. Each Component is discreet and only needs to worry about how to render itself and pass state down to its children. + +Sometimes however, at an application level, Components need to be able to share information or state in a way which does not adhere to this strict parent-child relationship. + +Some examples of where this can be necessary are: + +* Where a child needs to pass a message back to its parent. An example would be if the child component is an item in a list, it might need to inform it's parent that it has been clicked on. +* When Hyperstack models are passed as params, child components might change the values of fields in the model, which might be rendered elsewhere on the page. +* There has to be a place to store non-persisted, global application-level data; like the ID of the currently logged in user or a preference or variable that affects the whole UI. + +Taking each of these examples, there are ways to accomplish each: + +* Child passing a message to parent: the easiest way is to pass a `Proc` as a param to the child from the parent that the child can `call` to pass a message back to the parent. This model works well when there is a simple upward exchange of information \(a child telling a parent that it has been selected for example\). You can read more about Params of type Proc in the Component section of these docs. If howevere, you find yourself adding overusing this method, or passing messages from child to grandparent then you have reached the limits of this method and a Store would be a better option \(read about Stores in this section.\) +* Models are stores. An instance of a model can be passed between Components, and any Component using the data in a Model to render the UI will re-render when the Model data changes. As an example, if you had a page displaying data from a Model and let's say you have an edit button on that page \(which invokes a Dialog \(Modal\) based Component which receives the model as a param\). As the user edits the Model fields in Dialog, the underlying page will show the changes as they are made as the changes to Model fields will be observed by the parent Components. In this way, Models act very much like Stores. +* Stores are where global, application wide state can exist in singleton classes that all Components can access or as class instances objects which hold data and state. **A Store is a class or an instance of a class which holds state variables which can affect a re-render of any Component observing that data.** + +In technical terms, a Store is a class that includes the `include Hyperstack::State::Observable` mixin, which just adds the `mutate` and `observe` primitive methods \(plus helpers built on top of them\). + +In most cases, you will want class level instance variables that share data across components. Occasionally you might need multiple instances of a store that you can pass between Components as params \(much like a Model\). + +As an example, let's imagine we have a filter field on a Menu Bar in our application. As the user types, we want the user interface to display only the items which match the filter. As many of the Components on the page might depend on the filter, a singleton Store is the perfect answer. + +```ruby +# app/hyperstack/stores/item_store.rb +class ItemStore + include Hyperstack::State::Observable + + class << self + def filter=(f) + mutate @filter = f + end + + def filter + observe @filter || '' + end + end +end +``` + +In Our application code, we would use the filter like this: + +```ruby +# the TextField on the Menu Bar could look like this: +TextField(label: 'Filter', value: ItemStore.filter).on(:change) do |e| + ItemStore.filter = e.target.value +end + +# elsewhere in the code we could use the filter to decide if an item is added to a list +show_item(item) if item.name.include?(ItemStore.filter) +``` + +## The observe and mutate methods + +As with Components, you `mutate` an instance variable to notify React that the Component might need to be re-rendered based on the state change of that object. Stores are the same. When you `mutate` and instance variable in Store, all Components that are observing that variable will be re-rendered. + +`observe` records that a Component is observing an instance variable in a Store and might need to be re-rendered if the variable is mutated in the future. + +> If you `mutate` an instance variable outside of a Component, you need to `observe` it because, for simplicity, a Component observe their own instance vaibales. + +The `observe` and `mutate` methods take: + +* a single param as shown above +* a string of params \(`mutate a=1, b=2`\) +* or a block in which case the entire block will be executed before signalling the rest of the system +* no params \(handy for adding to the end of a method\) + +## Helper methods + +To make things easier the `Hyperstack::State::Observable` mixin contains some useful helper methods: + +The `observer` and `mutator` methods create a method wrapped in `observe` or `mutate` block. + +* `observer` +* `mutator` + +```ruby +mutator(:inc) { @count = @count + 1 } +mutator(:reset) { @count = 0 } +``` + +The `state_accessor`, `state_reader` and `state_writer` methods work just like `attr_accessor` methods except access is wrapped in the appropriate `mutate` or `observe` method. These methods can be used either at the class or instance level as needed. + +* `state_reader` +* `state_writer` +* `state_accessor` + +Finally there is the `toggle` method which does what it says on the tin. + +* `toggle` toggle\(:foo\) === mutate @foo = !@foo + +```ruby +class ClickStore + include Hyperstack::State::Observable + + class << self + observer(:count) { @count ||= 0 } + state_writer :count + mutator(:inc) { @count = @count + 1 } + mutator(:reset) { @count = 0 } + end +end +``` + +### Initializing class variables in singleton Store + +You can keep the logic around initialization in your Store. Remember that in Ruby your class instance variables can be initialized as the class is defined: + +```ruby +class CardStore + include Hyperstack::State::Observable + + @show_card_status = true + @show_card_details = false + + class << self + state_accessor :show_card_status + state_accessor :show_card_details + end +end +``` diff --git a/docs/specs/.browserslistrc b/docs/specs/.browserslistrc new file mode 100644 index 000000000..e94f8140c --- /dev/null +++ b/docs/specs/.browserslistrc @@ -0,0 +1 @@ +defaults diff --git a/docs/specs/.gitignore b/docs/specs/.gitignore new file mode 100644 index 000000000..aea2bd9c2 --- /dev/null +++ b/docs/specs/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore uploaded files in development +/storage/* +!/storage/.keep + +/node_modules +/yarn-error.log + +/public/assets +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key + +/public/packs +/public/packs-test +/node_modules +/yarn-error.log +yarn-debug.log* +.yarn-integrity diff --git a/docs/specs/.ruby-version b/docs/specs/.ruby-version new file mode 100644 index 000000000..fbafd6b60 --- /dev/null +++ b/docs/specs/.ruby-version @@ -0,0 +1 @@ +2.7.2 \ No newline at end of file diff --git a/docs/specs/Gemfile b/docs/specs/Gemfile new file mode 100644 index 000000000..9a3ef799a --- /dev/null +++ b/docs/specs/Gemfile @@ -0,0 +1,75 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '2.7.2' + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.4', '>= 5.2.4.5' +# Use sqlite3 as the database for Active Record +gem 'sqlite3' +# Use Puma as the app server +gem 'puma', '~> 3.11' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'mini_racer', platforms: :ruby + +# Use CoffeeScript for .coffee assets and views +gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'rspec-rails' +end + +group :development do + # Access an interactive console on exception pages or by calling 'console' anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' +end + + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'rails-hyperstack', path: '../../ruby/rails-hyperstack' +gem 'hyperstack-config', path: '../../ruby/hyperstack-config' +gem 'hyper-state', path: '../../ruby/hyper-state' +gem 'hyper-component', path: '../../ruby/hyper-component' +gem 'hyper-operation', path: '../../ruby/hyper-operation' +gem 'hyper-model', path: '../../ruby/hyper-model' +gem 'hyper-router', path: '../../ruby/hyper-router' +gem 'hyper-spec', path: '../../ruby/hyper-spec' + +gem 'pry' +gem "opal", "1.0.5" + +gem "opal-jquery" + +group :development do + gem 'foreman' +end + +gem 'webpacker' diff --git a/docs/specs/Gemfile.lock b/docs/specs/Gemfile.lock new file mode 100644 index 000000000..4a35bc161 --- /dev/null +++ b/docs/specs/Gemfile.lock @@ -0,0 +1,443 @@ +PATH + remote: ../../ruby/hyper-component + specs: + hyper-component (1.0.alpha1.5) + hyper-state (= 1.0.alpha1.5) + hyperstack-config (= 1.0.alpha1.5) + opal-activesupport (~> 0.3.1) + react-rails (>= 2.4.0, < 2.5.0) + +PATH + remote: ../../ruby/hyper-model + specs: + hyper-model (1.0.alpha1.5) + activemodel + activerecord (>= 4.0.0) + hyper-operation (= 1.0.alpha1.5) + +PATH + remote: ../../ruby/hyper-operation + specs: + hyper-operation (1.0.alpha1.5) + activerecord (>= 4.0.0) + hyper-component (= 1.0.alpha1.5) + mutations + opal-activesupport (~> 0.3.1) + tty-table + +PATH + remote: ../../ruby/hyper-router + specs: + hyper-router (1.0.alpha1.5) + hyper-component (= 1.0.alpha1.5) + hyper-state (= 1.0.alpha1.5) + opal-browser (~> 0.2.0) + +PATH + remote: ../../ruby/hyper-spec + specs: + hyper-spec (1.0.alpha1.5) + actionview + capybara + chromedriver-helper (= 1.2.0) + filecache + method_source + opal (>= 0.11.0, < 2.0) + parser (>= 2.3.3.1) + rspec + selenium-webdriver + timecop (~> 0.8.1) + uglifier + unparser (>= 0.4.2) + webdrivers + +PATH + remote: ../../ruby/hyper-state + specs: + hyper-state (1.0.alpha1.5) + hyperstack-config (= 1.0.alpha1.5) + +PATH + remote: ../../ruby/hyperstack-config + specs: + hyperstack-config (1.0.alpha1.5) + listen (~> 3.0) + opal (>= 0.11.0, < 2.0) + opal-browser (~> 0.2.0) + uglifier + websocket + +PATH + remote: ../../ruby/rails-hyperstack + specs: + rails-hyperstack (1.0.alpha1.5) + hyper-model (= 1.0.alpha1.5) + hyper-router (= 1.0.alpha1.5) + hyperstack-config (= 1.0.alpha1.5) + opal-browser (~> 0.2.0) + opal-rails + rails (>= 5.0.0, < 7.0) + react-rails (>= 2.4.0, < 2.5.0) + +GEM + remote: https://rubygems.org/ + specs: + abstract_type (0.0.7) + actioncable (5.2.4.5) + actionpack (= 5.2.4.5) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.4.5) + actionpack (= 5.2.4.5) + actionview (= 5.2.4.5) + activejob (= 5.2.4.5) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.4.5) + actionview (= 5.2.4.5) + activesupport (= 5.2.4.5) + rack (~> 2.0, >= 2.0.8) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.4.5) + activesupport (= 5.2.4.5) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.4.5) + activesupport (= 5.2.4.5) + globalid (>= 0.3.6) + activemodel (5.2.4.5) + activesupport (= 5.2.4.5) + activerecord (5.2.4.5) + activemodel (= 5.2.4.5) + activesupport (= 5.2.4.5) + arel (>= 9.0) + activestorage (5.2.4.5) + actionpack (= 5.2.4.5) + activerecord (= 5.2.4.5) + marcel (~> 0.3.1) + activesupport (5.2.4.5) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + adamantium (0.2.0) + ice_nine (~> 0.11.0) + memoizable (~> 0.4.0) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + anima (0.3.2) + abstract_type (~> 0.0.7) + adamantium (~> 0.2) + equalizer (~> 0.0.11) + archive-zip (0.12.0) + io-like (~> 0.3.0) + arel (9.0.0) + ast (2.4.2) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + bindex (0.8.1) + bootsnap (1.7.2) + msgpack (~> 1.0) + builder (3.2.4) + byebug (11.1.3) + capybara (3.35.3) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + childprocess (3.0.0) + chromedriver-helper (1.2.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.3) + coffee-rails (4.2.2) + coffee-script (>= 2.2.0) + railties (>= 4.0.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + concord (0.1.6) + adamantium (~> 0.2.0) + equalizer (~> 0.0.9) + concurrent-ruby (1.1.8) + connection_pool (2.2.3) + crass (1.0.6) + diff-lcs (1.4.4) + equalizer (0.0.11) + erubi (1.10.0) + execjs (2.7.0) + ffi (1.15.0) + filecache (1.0.2) + foreman (0.87.2) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.8.9) + concurrent-ruby (~> 1.0) + ice_nine (0.11.2) + io-like (0.3.1) + jbuilder (2.11.2) + activesupport (>= 5.0.0) + jquery-rails (4.4.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.9.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + method_source (1.0.0) + mimemagic (0.3.5) + mini_mime (1.0.2) + mini_portile2 (2.5.0) + minitest (5.14.4) + mprelude (0.1.0) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + concord (~> 0.1.5) + equalizer (~> 0.0.9) + ice_nine (~> 0.11.1) + procto (~> 0.0.2) + msgpack (1.4.2) + mutations (0.9.1) + activesupport + nio4r (2.5.7) + nokogiri (1.11.2) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) + opal (1.0.5) + ast (>= 2.3.0) + parser (~> 2.6) + opal-activesupport (0.3.3) + opal (>= 0.5.0, < 2) + opal-browser (0.2.0) + opal + paggio + opal-jquery (0.4.4) + opal (>= 0.10.0, < 1.1) + opal-rails (1.1.2) + jquery-rails + opal (~> 1.0.0) + opal-activesupport (>= 0.0.5) + opal-jquery (~> 0.4.4) + opal-sprockets (~> 0.4.6) + rails (>= 5.1, < 6.1) + sprockets-rails (>= 2.3.3, < 4.0) + opal-sprockets (0.4.9.1.0.3.7) + opal (~> 1.0.0) + sprockets (~> 3.7) + tilt (>= 1.4) + paggio (0.2.6) + parser (2.7.2.0) + ast (~> 2.4.1) + pastel (0.8.0) + tty-color (~> 0.5) + procto (0.0.3) + pry (0.14.0) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.6) + puma (3.12.6) + racc (1.5.2) + rack (2.2.3) + rack-proxy (0.6.5) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.4.5) + actioncable (= 5.2.4.5) + actionmailer (= 5.2.4.5) + actionpack (= 5.2.4.5) + actionview (= 5.2.4.5) + activejob (= 5.2.4.5) + activemodel (= 5.2.4.5) + activerecord (= 5.2.4.5) + activestorage (= 5.2.4.5) + activesupport (= 5.2.4.5) + bundler (>= 1.3.0) + railties (= 5.2.4.5) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.4.5) + actionpack (= 5.2.4.5) + activesupport (= 5.2.4.5) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (13.0.3) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + react-rails (2.4.7) + babel-transpiler (>= 0.7.0) + connection_pool + execjs + railties (>= 3.2) + tilt + regexp_parser (2.1.1) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-rails (5.0.1) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.10.2) + ruby_dep (1.5.0) + rubyzip (2.3.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.1.0) + railties (>= 5.2.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + semantic_range (3.0.0) + spring (2.1.1) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.2) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.4.2) + strings (0.2.1) + strings-ansi (~> 0.2) + unicode-display_width (>= 1.5, < 3.0) + unicode_utils (~> 1.4) + strings-ansi (0.2.0) + thor (1.1.0) + thread_safe (0.3.6) + tilt (2.0.10) + timecop (0.8.1) + tty-color (0.6.0) + tty-screen (0.8.1) + tty-table (0.12.0) + pastel (~> 0.8) + strings (~> 0.2.0) + tty-screen (~> 0.8) + turbolinks (5.2.1) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.9) + thread_safe (~> 0.1) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + unicode-display_width (2.0.0) + unicode_utils (1.4.0) + unparser (0.5.5) + abstract_type (~> 0.0.7) + adamantium (~> 0.2.0) + anima (~> 0.3.1) + concord (~> 0.1.5) + diff-lcs (~> 1.3) + equalizer (~> 0.0.9) + mprelude (~> 0.1.0) + parser (>= 2.6.5) + procto (~> 0.0.2) + web-console (3.7.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + webdrivers (4.6.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) + webpacker (5.2.1) + activesupport (>= 5.2) + rack-proxy (>= 0.6.1) + railties (>= 5.2) + semantic_range (>= 2.3.0) + websocket (1.2.9) + websocket-driver (0.7.3) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + bootsnap (>= 1.1.0) + byebug + coffee-rails (~> 4.2) + foreman + hyper-component! + hyper-model! + hyper-operation! + hyper-router! + hyper-spec! + hyper-state! + hyperstack-config! + jbuilder (~> 2.5) + listen (>= 3.0.5, < 3.2) + opal (= 1.0.5) + opal-jquery + pry + puma (~> 3.11) + rails (~> 5.2.4, >= 5.2.4.5) + rails-hyperstack! + rspec-rails + sass-rails (~> 5.0) + spring + spring-watcher-listen (~> 2.0.0) + sqlite3 + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + webpacker + +RUBY VERSION + ruby 2.7.2p137 + +BUNDLED WITH + 2.1.4 diff --git a/docs/specs/Gemfile.orig b/docs/specs/Gemfile.orig new file mode 100644 index 000000000..55c5e8fdb --- /dev/null +++ b/docs/specs/Gemfile.orig @@ -0,0 +1,72 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '2.7.2' + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.4', '>= 5.2.4.5' +# Use sqlite3 as the database for Active Record +gem 'sqlite3' +# Use Puma as the app server +gem 'puma', '~> 3.11' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'mini_racer', platforms: :ruby + +# Use CoffeeScript for .coffee assets and views +gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] +end + +group :development do + # Access an interactive console on exception pages or by calling 'console' anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' +end + + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'rails-hyperstack', path: '../..' +gem 'hyperstack-config', path: '../../../hyperstack-config' +gem 'hyper-state', path: '../../../hyper-state' +gem 'hyper-component', path: '../../../hyper-component' +gem 'hyper-operation', path: '../../../hyper-operation' +gem 'hyper-model', path: '../../../hyper-model' +gem 'hyper-router', path: '../../../hyper-router' +gem 'hyper-spec', path: '../../../hyper-spec' + +gem 'pry' +gem "opal", "1.0.5" + +group :development do + gem 'foreman' +end + +gem 'webpacker' \ No newline at end of file diff --git a/docs/specs/Procfile b/docs/specs/Procfile new file mode 100644 index 000000000..15e9f0c92 --- /dev/null +++ b/docs/specs/Procfile @@ -0,0 +1,2 @@ +web: bundle exec rails s -b 0.0.0.0 +hot-loader: bundle exec hyperstack-hotloader -p 25222 -d app/hyperstack diff --git a/docs/specs/README.md b/docs/specs/README.md new file mode 100644 index 000000000..7db80e4ca --- /dev/null +++ b/docs/specs/README.md @@ -0,0 +1,24 @@ +# README + +This README would normally document whatever steps are necessary to get the +application up and running. + +Things you may want to cover: + +* Ruby version + +* System dependencies + +* Configuration + +* Database creation + +* Database initialization + +* How to run the test suite + +* Services (job queues, cache servers, search engines, etc.) + +* Deployment instructions + +* ... diff --git a/docs/specs/Rakefile b/docs/specs/Rakefile new file mode 100644 index 000000000..e85f91391 --- /dev/null +++ b/docs/specs/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/docs/specs/app/assets/config/manifest.js b/docs/specs/app/assets/config/manifest.js new file mode 100644 index 000000000..b16e53d6d --- /dev/null +++ b/docs/specs/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/docs/specs/app/assets/images/.keep b/docs/specs/app/assets/images/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/assets/javascripts/application.js b/docs/specs/app/assets/javascripts/application.js new file mode 100644 index 000000000..e4f578461 --- /dev/null +++ b/docs/specs/app/assets/javascripts/application.js @@ -0,0 +1,17 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require rails-ujs +//= require activestorage +//= require turbolinks +//= require hyperstack-loader +//= require_tree . diff --git a/docs/specs/app/assets/javascripts/cable.js b/docs/specs/app/assets/javascripts/cable.js new file mode 100644 index 000000000..739aa5f02 --- /dev/null +++ b/docs/specs/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/docs/specs/app/assets/javascripts/channels/.keep b/docs/specs/app/assets/javascripts/channels/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/assets/stylesheets/application.css b/docs/specs/app/assets/stylesheets/application.css new file mode 100644 index 000000000..d05ea0f51 --- /dev/null +++ b/docs/specs/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/docs/specs/app/channels/application_cable/channel.rb b/docs/specs/app/channels/application_cable/channel.rb new file mode 100644 index 000000000..d67269728 --- /dev/null +++ b/docs/specs/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/docs/specs/app/channels/application_cable/connection.rb b/docs/specs/app/channels/application_cable/connection.rb new file mode 100644 index 000000000..0ff5442f4 --- /dev/null +++ b/docs/specs/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/docs/specs/app/controllers/application_controller.rb b/docs/specs/app/controllers/application_controller.rb new file mode 100644 index 000000000..09705d12a --- /dev/null +++ b/docs/specs/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/docs/specs/app/controllers/concerns/.keep b/docs/specs/app/controllers/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/helpers/application_helper.rb b/docs/specs/app/helpers/application_helper.rb new file mode 100644 index 000000000..de6be7945 --- /dev/null +++ b/docs/specs/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/docs/specs/app/hyperstack/components/hyper_component.rb b/docs/specs/app/hyperstack/components/hyper_component.rb new file mode 100644 index 000000000..f4abbaab2 --- /dev/null +++ b/docs/specs/app/hyperstack/components/hyper_component.rb @@ -0,0 +1,13 @@ +# app/hyperstack/hyper_component.rb + +require 'hyperstack/component/jquery' + +class HyperComponent + # All component classes must include Hyperstack::Component + include Hyperstack::Component + # The Observable module adds state handling + include Hyperstack::State::Observable + # The following turns on the new style param accessor + # i.e. param :foo is accessed by the foo method + param_accessor_style :accessors +end diff --git a/docs/specs/app/hyperstack/models/application_record.rb b/docs/specs/app/hyperstack/models/application_record.rb new file mode 100644 index 000000000..0f9516adc --- /dev/null +++ b/docs/specs/app/hyperstack/models/application_record.rb @@ -0,0 +1,6 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + # allow remote access to all scopes - i.e. you can count or get a list of ids + # for any scope or relationship + ApplicationRecord.regulate_scope :all unless Hyperstack.env.production? +end diff --git a/docs/specs/app/hyperstack/models/sample.rb b/docs/specs/app/hyperstack/models/sample.rb new file mode 100644 index 000000000..fcfe551ee --- /dev/null +++ b/docs/specs/app/hyperstack/models/sample.rb @@ -0,0 +1,2 @@ +class Sample < ApplicationRecord +end diff --git a/docs/specs/app/javascript/images/hyperloop-logo-medium-pink.png b/docs/specs/app/javascript/images/hyperloop-logo-medium-pink.png new file mode 100644 index 0000000000000000000000000000000000000000..ba68f59b1eade3b4936429af6ac1a4907912688d GIT binary patch literal 16477 zcmXY(1yoeu`}JYyMtbP(9J&RO4(aZakd$r&0qGnXWC-a}LTUi%5)l}rL%Ks6-uwOk z-o=7p4QuW_bLTwg>}T)KCGL%;5*{`sHWCsNo{F-94iXYF1@QYFCOYsl(8s6|ctE#N zQ&K>B{_m@}t0EnE2FpX)*c%Cnm+8N6WdAZ*U*JiM_bM8%G1f7038*2OSe~t?LvZ)8A2g6!bi^D0>Oi)w32aUot_($y0d$XX2RpI%+mOgor-EPGOQB5A-_?2`jqe{%MzT1~;hUXnCP)IVYfKr_i*(*CFtG^=e&J zU7a)RB{VjY(EjWFi3}VBkAw%pP2do?UxeICx?5*SejI-c3Y=h&6jmorFq*od;*?hr zO7NyJnln|aU6`UALTk`X2Hcz&@jAjM!agGOCEx8G^_usZIJnspPGLpKuK|{>-~w}; zYJ4R%=Xdvti}Y}^VzcsQ$Ru@cyXt`Zui3A0Sd~KV;^mDFAZ{oTbV#ch1{iZBUX^6- zpl38%^rzZ{(0Yp2NGs3|eQ(i@@+mhLcO;G(SWF41=*$jAJGF1o|G zRw57Mz$_=~l?IEU8+Bsj36N6YEqzING8N!lPMQyeKfyg}+-oZMU$iNJAMb7Sup{?I zEqXKzdL8=g);!jVUIwigDtfN{1NoxYA(6Q>v94eon%~TFtCG-T_svG6YAEv#qkbpu zfHi87M18RBP4rW1(qumi8tph?3h{I#PQzh*#q_!y8;|wmj~Yo7Ra^u9yVUX??EH#^ zZU)CqA0!X#2Gw|>0HTH%$lacli$FUwMT}Wi)~v@gS#Y8NJQRK~y8AC7Nte%xWz8MY z1~ceqgZpJppZv|V^KxVq--76km%KK;)Sv4gv@@edTmO_4R`Pd9DYPEbjQga`BEEaF z#D*7-G>1zlJ3nRoL7ul?@LW|ld?sTW+Rpsv(+|F)WYkFz+34_10=ft=2iS$O6I}Qr ziFFyFnpP6N@#p4;)8RKe=7;z{$r13HKpTd&DQ!X-8sJQm3h#0RQb|Jx*fERHpO%ok z_EH*imIe?S8I4Ek>&E>N#7!mkWR&XH<1H8cvn zieQPzY}~OFVR;;4503R2)FkQKdr!8~ zi?BuGvP!0UK&6^6$N5}akr2wE^_0DoqpVFBipj10VbDv&1D{_Byu5z4B;xv@h|xfG zieoh<(dAGzAipOw(tH?vG^Dm~?)_e@ec_%aH7A|Gq-WRPCkvn&`RiJ4snR zMLzv(ETr@uacQknnp(%@o;ZACbhkER+E00P>WYC}={}psw&&xpys(juT?2nA zWH$)RS#{>6zqD};H+B-=OWQ*&p{I`0pz>gEaHsjcW(n^d-JJ^5$@Bc06&-QV!1jA) zZ~~qQH?qIn`FkfBeZ5PNkFNKM%sswRii5yGhl5~?5vARSbn90fo{}t@@;i)|-~y6= zQ$R&=!LCuV$X7LUZ(f`_lSEZtMxltU51XS=ac7IPC!l5-DxPpv{E|H>?{OKxO^xF^ z^#k!?xE4U)9Y~1>tY(ZHd%mi&hV9^}B#B=J%v9mwxp-_VHu5$;9%{G5EE zsQCW&>qj!Qb!j+$A+)P3!GeL{qAtTzCob$n2d%vm|8RPEcEx;=Loe`b)hCw=FhkC zjrSO(@9(c8CN|8=GeSsst$9(T&~eo&mo04`t-rGgW$~`~>8>$6K z!i`S6A*Y0=dRq0oSc!_Zx-u5fUq>;>Q;KqJrn#XxsO^X7QhAK+W*9XKc3U7{!#&wT zXdO$F@mqc#P5vIV$;;q*yv%7>?!=OJFt|S=3?;tQnZ9JU*|cOBU! zNHZz&lxQKeRyE!8!Tb?M--+50ZOYX?j{Jm=o9=h4Jd>f8^Pq&R<puMkT-Dwqvr!;_825fa@6Jw;dFCk|((1jxCo`HCg(ltAvJe?*F@mPrrPo zbn9X~H6n=$VuxAQq#Vv6AH!uy=;OffGL^75I2v%@)X(3f>@OE;G~w#b{F9>{IYai% z;f~c}6~BZ|xQM+mTv@-FG)SF#_%%|-QNUt!>7iQTG&WvN12=@J1&*(?I0qbR@4DOn z^$r)`rVJ<{mSF^@IYzHx*>n`q%X4*T&Ee)5)7NaV+@UKiO*YrnXX2X?cp1}zrqY>m zBvESEBYMGK48R2o0Y;ha16LF5Fh`IJ3Ut^HL!7RsGJ8%saz^STw1u57_UXYAlw0MEG&M_GkOK>;)sjE@) zb%8NGxstENW#=UZTJP5t`ugpFy{@{u)qQ5BT|LET8Y-MiW4zWwynHIOb%v7me6@E} zs71(m%8L?$h?DC;i|-C(WUXd|34+51{uXy8WcTtyAJ`&z8o+lhy|d@KID;sr?69J7 zk9}oJV-ra(<~)Tglq1%4ZB*&8B#{cOJKGsWx0zQOZFC4}f`uz-Q3^);%b(o;UKzAj z?hNs%hnV1_(&iv!iPD^ejzPW#ic`ZX4k2_o`z#PsLiKnn&xfQ*S*VYbALzmXCCEA* zDvIrh5;W3$k*2+>RDKsbPKG+eNO4%%8?35HpQ|qEA@T2}jkJr1&#<7Tu43y>4QooW z`c+ByUz0#wV`6#{JA|BPp? z$j7*b!Kr!Ay-3l{m1AMXepZV-@73$K@PP)h1j) zaBV)7CPQ?=hq zh)n+*dWqVJ*7*UC@E*w&C9sK_BYR$&9A$fm7Ga8Nq!^m@BWv3T_ePr}>T}S0`c0$h z%?=tkIWVK~%hy{q+SHQ_=GYnsy30Y9>@XKHSNlu5+Zzk;`5po8k7I7UQEgeiqqAjS zNG^oAnpG#JB2I{zfQZj@Mhm%~69y9+E(4wCFrZ$3gaoQ5QFqBmdKcIUkyU$~n{cOK z#A%VZj7>BLVah=C#R5!m=>qW)zew0qc!S4}#TlD{F<^GXr!blWUFAvG-&BJ+Pb;0L zH5CgwbC@aYFY}#U|Kt}v@uZY+?8f2$-v!0AaN>v2Q=6zKgE*W7UL9~$qxA}56ka|& z-3|O^Pa&GCZ>|BRHR{chVF%Cl>xi%1WfEh~a{L#|_n^#7kO*}0BUkVDjGv!Xfwwz*rU1Iq|hKc>$9YV<_w zCy?Sj&#vuGV~p-D7zQ1<}5JLsDyg=TSXBk(g~7q_VCn1 z68975w@6V|ZF2(fo)oG_c5X6Hmx}4LLBf+2cv9W&;Zw~u!j8U>xEWVU5mLJ{qqveK zg^2xWHM=i5ooMP~UWfW~h6jA7W6c7DSZ!Qyim|z)SMh(v*a>O8;>A=1OOyN0Uo=U7 z3eYK5y&0YCV1xmm2*1S-KGE^HH~A`@Z^WsMZ$A&&(=g`cDAHnX2Rt+_zGM8OCE$&% zSH&?zC;G3grT~}DYn-@O9nL4~Uc`<$U+*!n|IZ!G#Db~PKlrsn@P_4f${7X4u$wuv zpU+-Gq=(QciWblIZ9uJ&HX%Ox9MYf6=_xW@K}kv#k|-{#i|+}BiiS-y&2c-nid-T-sZnT3s^o;)*EfmDfuF&+N!5d84MkDqH{Mc$E;wamx@{OCJ(-@v2Pcg~VYCm*jCz$=t|LQ|g5&3RD$MstKkBvS;P z78VrtmxST7>IWf5b{NLiXF9b2LXuu-TIf|(xEU{Uoo0LD#8Oy}lOKx*Spr)GZ0GDM z`ZOM?r()s0{-+hESwwzV5ng`ouOmxTK*eimG%`F_7Rc1I!rg}Ifq6lFE3`v9^D+UY`R|B)a*(!*tqVNJyTvaKdf z!_)xtwLwlOA|o0j*2FZKBzutyru3T;vxKr0xd4mj;N3&={X9yTz9OBGB2K3gro#}V z`_n;0k%-))5*JL=evM2q z{xt;I$~4B+8Qkg@v5Mq0RL{){xi1_(ZPzAr19o8gd2T=}4mxwYfQGDX-;jZ}>n}Dd zP`Ef@Y!#=pYRG&j{h8DC5@D9cta~77!~_l(tb+M#x^zSjTX!oIa`*2?HaBYU+=q8X zsKHvX^lme%h~kmi%xSt(e&2Tp%q9K(2VTL(4qXByat_!~Yl)(+v#-3T%r&weNfd^H z+>Hc3z8Hm33Gx=mBLx!ZrOGw(2&X^8TYoD+hs<~N#%~HVr>&?Jx45bBbwzLcUKF?x z@-^V$>k6cU;jTc?W|W=Tg1Fw@-P6|x0U_1~uh7(^4-uZ)gx$1g>&Os{yNo<0v|c?& z5MFGghpa38jQ?6a{1cwrFCRc0>YR2EqOHdvI%|>G22VI-Ev;Jlsc7V7$i?Kqk2>;9 z#;8=DCdUaHbZZv3lg|${qr1>>;tVq!ZhMwZtEv z81FYD@?i8!eAs=+6K?XtwQPjI6J;>~r%lT}u-}Wziyy(I`elalGGECWQXD zUHJs(!V6vP$e8Y6)0k$*R08EqZ2lR_n3iSVBKB8Z4XmZ z4Rt+k@CT0z@lF`pUCEmto!7o*8{y!U2MNA?V#gGXjXYI-k)0kMUZ`OkP*!8)B<4%7 zv#9@^q#S&bDD*pkK#DS5P2O9o7377C;KuIrPUoszx|l}q9*RYDdV3gcNWKd1ck#Iy zz1M&_ZiWSpEMZ2}m(V`|ZGAtCKEEetyh2BdNKKwb*2o|#)kah_nlXC0pla5Q4ZDxV z)7O#Ol@iSw(y~#4-nCjiJm(d;n~bV?);7iArJ>Uh#yRyCH8gYgnlxO0Echgw9n()e z&d$x9Kh8I%-|kq7-5YCOHVcQCqfvfvYL%ufVVisL`!~qlXA|jbVD%$vc8ufITYi-> zJ4@LKb7k}ioj^r zmR9#}WItK#)PKb$=3wWqMxA%Cd&G=40*#K@a8B3)V|EnBd-i|X_jqspOfe$`0(m`R0b7qox*jAceyebUs|r7+*Cqh;gg=TPzT%M=oofyctr=x&cVN)P|3 zn5DZ@MRZ3**Np?^w1tf(b!MdcdmE;-R(mYDNW~W!H+`g3Voq&YAaw2~d3dj#DOA;o@hMz6VdyZBzSBBANTId6OhWsM}un zw?u56i+Qj%@c15_^T(K_L>y0w`&ob6O52TL;$kKbw;V*_ku6Gna{EQG2ABfW*KgS8 zz(Ma-aWwOm4l0fXGp4=6ZB=^o_TK?!(vQ)H1MK?cpFW!NvCZFadz3P#n+rxBIw5yE zHdXJqn(q8RY!8mY+1CcyF?&qin=5NAwixlcjf_Ixphz6s8b`aMmUF@!$6+C>pMzJH zTQjD)N`Df#*c6m~kmjYTO9-8G+T2GR20A%;k8#1+SiX)umQL+QxWBbO@BEB7jC88` z(;NX98uj@yi&!_%A?b4Kf|*TSii-n|ur==x`%9c+ibNfzO{F>cV5H5OB ze8Hjc#t%qX9(R*S{fOi%u(S#|q#I&lP0;`3Lfc(nBA~pzYYK5xi)&w^M%}ETMTw{m zuTT+vRwcRy-GivuVSWaR$?A6@oXq{zqlnSx%9al_a;rkDI9jfBXrEQTo-I*6G9Wp2 zO!u8}yf^M5tJ@Ug!8O|{#Ln801?3mKwG|edr*RiY7$yD-;BRo^DUUQ0MQh_ZvozF) zIE|rlCg~}j;JP9mVk{XBd75Z$AL;`or#@q+I-&3>^7Q@})|CX76nUGX>oR*{8i7~U z?PC<(R`NHot`d6g5ozd`jdNo-@96G5an%KzUZIv)Tn{IVK%I~t=o@ekXXrbg$?Pw+ zM!$aMN-xm~`=yw)NwVh1$TYjgWR(s14pYqeLwYctTg5bq8u{%YxlKZ}_b%x>`M1I|U1 z@giU}Z`aUUTDQto_bRJU&t}E`e$+)7L9?O@D0fk8!8%KU$MA+GLO%{G~K%YSn zU)$XXxqE+2XiAyH-adpIfDb=#+wbGNxA(^;zJ6KUx(SHCWB&28&Z+mt)6b3{hqI5= zHnl(~)TC(Z-IIs`;!Ve!p^w2ycv0`!=F@IwKOTR=jG1RQ^u&i6eRM2kuYV5u2%Nx` zr<{fctV!HM?$^8VEnA(%(#8|j$H!G8TSF(f()a8GuA!oPSweTR#7FC~Vrj;yt` z+VD%q^umE1iQIRSk2z+1TCecSeimx`HA|QB`h*>KwMbXyG;xFEORf24L1Kl+d`O_a z05P4)nLs=W%x*GhYA8)>Xyhcl5j^>elUmp!+Y7_~(!37KzJPsE8~HTtMewO%!{ zPekHYp<5nr{pp_r1ziuV8T=oGx0LczwwNA4BvB~D0=iI@#hqxdr==LAsrjbRSp#s0N||@d1Z;XIb+}!dWqTO=y&U=LAEwTPfZU-C zn&UWn*Drvus@|LA^zjhQ{Z$Qj@LaJ{f`?AFZieKd#Q;)V^K!?=2H)35)AdozLjyCE z;*?}=B#(95=D>1!e!uJ$Gxer|E1?tRQ|m?MH0fU)=jtEapwM_}K5dt!aY+P<1#xM% z{up^ql`Rj9uQBP5z+`jQ57rj^(YGH=xnP#+Vm_kCiluKS=Qe3tbroe(-(E`~mPfX# z?x`DCPTOA<2b~?QwR}BIf&{jcL8scZ78@`ep9>;8eL)qva)S{%em@w=j}w=@>!Zya zJ`FbINA>s_Z1Owz^>*ank*ZSBr;kjiisZOUo^FGBIA8JhhnwbQn{D`Y3S;+KW5xW9 zFX5Tez`Q~M#0BwF(}x2@x4nIWDVdv5IONWrVI1cR52W)oEf|rcwHiu{X1J%30?~pqa!(!&UYlC0Qf2+yK$kXHnNFZw7}_8+9n6mEg58WB2KIyv-cF)R|xkY{dQ{3nT*E}Y(%Nl-to@2HMSE^@u z$|#))dA=bmm}#MDnEp|V4TLoKSUyiE0w3HoYx+8VBFrY|73IW))R;Ff&-M@#P$&Hr zXTk57mcTmjbD~m)$fN4URMCE5UZ*_l7^TH!jY+bh5hF}9S3Yj4+R88c?yEof6!P3B z7vGLv|H+Q5s4W6lk3~gxSnE`=`cOzG@;@WhZ__J)7fHvX#^2|F5^dIPIxw+HD(q$U zam3zpO;o6b(2R^$Z@WNg4HB5hhw6aueC2GDWBpm#+Q`-jvtaz5*G7#uLV z;WWRlml65tI3@hldINve1~Vy3=u4S7wXr$`*WloZXBFmY0Xnl9^7xcGIV8* z?yIq!<*{R`jlOkZ1X?g0k?=7`j15xPl47l9=X75ND=sTtu7Bl?_=78OywTliVo6Q{ z(81|M!yR{V&)cDA<;&-9@N>N5dy&0Wz%W}KUx@sUDFWN5qJ>WOZu@@k(7=~7kEj*e z`Cy(o?YHyB2bg31l~S^5x&Ab!U%Qw*bMIGheXprqCtx|6&-IDY;>*N4)j&9EnQG7G zF6z~Tb4oBZR)>nuXC0sAei(3O3-osv+kT#rHFbSiz3+swVuwcK@Y)GX-}AcMc&qa! zHDNwUjO#M>D~+l04#M_YtR2{g!^hn{;?@JJTdfBZ#Dodn4B zI|1{`Mj_}TCD$L&WkZ#^yL|k~m-U)mrGM>G)OjYvWNAZRCC<<=Xk?#LFN)*$x*cV;-@yW+>jI*T=`$RJ*(Frcrj?&>@uw zSB3A#Wj-N497u^0(;{_yC1Y6mC6PJ(yzt8q;8urah!sa!KBefGwYI*5mj8-}cmI~n z2zg<(`2$>Fe4}J0g5!YFIv|;GuK7-g_0f^7q24s-^GoB~;}4d~YC%}0x)nO(F&gK( z#W)&@sFx0FT6q3jGI2Ytw(;<9HnesqQh7hIp{!kjtIO+J@QHz>>CV^GJ(qa8YB%pB z36Ko9@7jaXUNA^w`G1~FUPLwGd_jD{eQJ#3yNs#w_ILw|=YNa?SD9=h-KFH(@YCD8 zB??`fLVV;*k%9u=U()o68WO28Sx_Nr8-!Z4lxvtu+(Z551V%JNh6;W_t*GjO%iQdbQ(P*Y}+ zgH$Z&pqG4aQ{8e=A0!)%cs@vb>+_k>+v*H^L_?^j0tgb&WcYvqqrjy3EM$Q-;L~xv zQ5Wisu!n8|?FEgMk&SS7UcK75h+yFljvJPv)!4fugB505?YC>0W2c|tcZJYI14XTq zBg{?fo3e2!U}p|PA&%ECE+H*O5kt;f@w&>O6U4m%oIq*EUjPq+{qQBOqk@Jiw@7IE z;G2IJx9v~uw|u00NuE6XbmK2H8|4H?cVo^7JT0-sEQ9f0N4eYE34*DjM62;zWf3gG z_LtmNs8<({r4cy@v!8Wu_>@ALx!#69Kik?Of+jr2(-OLrsXwalfZanp$6E+Es})fi=EVtQo0n5r2%{9CpBRWrb#?XT*Z)F6 z3F^bcjBo;-;6diS#Yi(wqtmTxO}MxIN;%3+CM2+YalQ=FCrc^TJsNX~b_}>+DgH#H z3Nz9pz@F2a6}79pHz$h}`J>cX#POtAY?h+5AP?!4?)NZ^mfypYXzlN{xRZ?EV*)nK zy0b^8$B^ioeff}{QuODSmTUX$n0%;#q? z?BC$r%%Ac>(_?NkZL+ceAv>-kLZ;h?+T}Pv&mOmZ;Iq2O;2|HHM%zPo9Ku3 zEU9KTD?fbv_bN5;R^<>-1J}2mOX)8OAKx1g^lXUPU*5-gu7Zr(2Jd}A9e~YD0=asZ z<0V5E%VQHJpBRF7ierD-b@7WtjkK<2K31!2PEe^DY5uj`jWS7;oVAG0!bviUZ2g%? ze+m5(e`D^d#^z{MM!&sVP^rzPuilAS!kPD8GwAQcX8>AK3O@+Xbm@FY<&p2vCnY?f z6OH#ySJ9H&*^d(AN(7@^LC&A^-&coZC!shZu!c3KifJ8+Tg|l4vIKUYh@Yg6-ezGq za-qiM#mk50QWj4bw~0`^)9-XhrvLT^WT^a2$Z1WyF?@Y=H=av+&8+IpspRXI&+^|> zvm8;hl;my}A^X8h;LrhwrUm<*idSE=wS||Qm5MEGHXbpu7g6f3h)%t162g?8$sHoU7mU$-gH`z8aK`4PA$=P<^OQ5Eojy#~`t+Cg_Ys(^<0VkFpyYI-8 zQ)4d3W2@lqS5)+7S-qom*TZbCaDPM@apV)Ow64uRO^)h~wB-@ur4uGAD*MYCR^euM z_U3d$_rx+olBh*shzxV9E$>y6Z?W^-cj>x2yvsOQnMZd8;JJEq24?tbR#S3`fbdyE zD9s?1EKnP90`_j+fS&5*mC#}8ZI1rRe%p_bikX`#l;(z=1hWfkVdF1Vz3Nua4jKqx z{xiapE43{0+{dwohP)cyzP}zdQ^lD=cHx8#>CMcSQKCL;xEkH*b6hX`Z53_;=>=eS z>2$>^9uC)T6%C2T;}u;3l}lF^IQ2YLLVsA`Ur$BZl?y}=Y^aynl*fLNmnyo6P^KbJ zy>kO6Hd{49H9!dqHpbo8S4A{*^ku>cGN#=P)V9i;(JU?A1D*x{p~%0lB$hSMhEpem z$$%rU^pb>fNOOU|Aj9X*D`o+2wj1p8Zs^%3fE0R-jIX?Oz(E=Geoc!PtRP20kMW`! z{Y86K#DX@Nvw+n0+~CwSWm@&nx|AXIJoNU{wjd)5}q_zjbp9;? zu`??O<3p~A*4XM+LdkssiF6I@eswof4FV}UCZcx}P-CjsdhZ-YNOL^ckwp0cSJ#mw zDufutpsrT7G`L{7I#2)Fm--MEO%d!h{D5euU4VpUT%P~T=nu2?@*G@KD%|>o{O~nr zWFVl`l64sEtk-p>;FkE_4Om{du=^zK&f4mcw;=l4yVsjf8m2++)s7WIg>J4+Ka1us z0@iJ*Ux2v~zKU*BY+)5qBX2C*>u@c7nz2!cj7!FvT@4@#3t!?}o;zr3s_8jcx z$RI!D#qysSIYw+7@tTXO@>UnB)BY8Zz+{7w3NEa!!o78e_g1#n(|uT9Ev zDfNoD(cKR~-gE1ubZIs7C!82q=8B^sCp1`{;>jn}huEJ*cjhX}7oLL3YuM{;0*pIOShBWa7-71a z*9r-$ZrAPax`b{}kXMl4;rb5@%HJ8102YHdq)cdT76KiN?PJGe8lO4q-`2lTY-NkJ z5p}h`uLG!oKn1S1F`DDH63f>_}6@ng<=*fxJl1q_Q$Gx(X9VQPKC3ahG8ww$T z9I1czYD+8alB`BwBt8V?L+*lI&zceyx;ilr2&+j$BJP27w|+roP#|;L%FlDO>zNE5 z8Ap0fRyFo~p8&KbC6@U6^6B}p6K`ZY1`Kw>kkraOisBg4^WqYYs}f`^!P2}8W3@8U zrt=C_eF+i<=)!nX*sjy6ARuW^ZcG|k`8%HbW1$?Q(fUYc|B-BTH#JV?`I9?aR&KmB zx`#{rNZOQmRlvGfrC`=A<)1QcVs)Ff7LBtxAjV%bWsp&fx-mGkZe_;Tx?IqEa1qdW z1H?~K7cWO0YbEu;AuiRIc@HZ&?`_|=9LfKO1ij`N6u|iP;{>p%I|oKrEy6S0eLSon za^#b81K6GcIkPvrkkyWa!M`BQv6Citkt4_4P$-Xyy(g!7>1dQa&8VbAN1wM1U47C7 zZVy^Z?5vfQTz5{D7oPp)7>_~=a?5vE-sC0FEZJjSIDi_Q(OL`eQFY>e9i0r_Y=aBP zOZhZ#j^I3)o#XR=6CG`nrPTW+9O(vGM?#HP=7(y`=A|`L^)FfJ5Ca$YC(1Vu7w6uE zcHE|vMr3(2wi-1bj&s>Duk{ssmKj-LFT%#X<+H3Vt}M2<<6e00*I~N_b>y`vE4v<` zwyO^m9tzho@&j6gId{QsFXq|C8>UXc4m9}rP$V~KB~Cg>}k)F&K| z_q(Z(HtCi$NsR(boOt)|LabfaTNyx;j_o(|Q4MbpL|{{g50 z?Ly2yEY5}>IcKf1{Wzy1t#bWe@5Ll`*OF+>*a+}&XyG>mSfrQvx(^Nh2w%1cd6O2_ z&YISzVw9T$wGaN~x*xEh;CiU&W@xowtV(nxO0)WvK98)T(v-Y9FxXGzR5giB3HpOc-2OO8nA-l*F|J<2P_ZXVY32*A zN>8Mv)OeNgae58$I4kXH0dla(8&#;k*?1{VdifW~_vYQg=XKrGvcTcD0Dbt;ns)UO zbn_*f&b)5Y9eD@n8lZx;UT&1_H<&#IrPjvB3EYwa~_*6pI?rirdG=Q5BR)WgYE`7 z(=x9tm+e;4#8aa|?uPa9b|Q^{7r-Xct-RLPGJj+DZ%kcfKP=O*0QQe^5(o^F7bT=bQ^zy6(H9jQ*Q;^e}7O1 zb%V;S(lrK{iHSAoV&0;UY-JM!J#nQJF;4>gaR$t+3m{sFasZ4xdSy)MZ+;#*u}WwW zN5w?4TChFxtuf@Ul4WUv42)}r8wR}fmYT_dnmeoDO{?v#VFgB@CAYskgZEILwuN!P zl9MAF5`(RicBHsBtV}R*Hg-OY=oc5HM;z1d2PxRNx<>xxKPUju>Hp5h3LADQIpIKT zTwNhv$A>5W1xKs@B|+=mqS){#h*(L|HINLLnAb~b`s4tumC?^9A1A=3_bu-YCXlG++YXI zhO{9+N#MgFu6(`L@mfrXPp60;I(G1qmp2sX!tImFGuR@Hmvk)z+iMH-BX57}e!x0s zyuxo!K*D#2GbN`u{?+(!mRi|v_tn+Y0E@M6%Bdyw06JW?&DcW@G>^;;?@;J^-jPHx z0m4y??~!^;FFN~`qV7k#G^>p1<}>ee{PA;asyu)*;l-1}b)7?*K;Zg|0ja352$UL3 zx$9{hGwdqI&l7c4nC>x=kUsVikEQnbY&Of-|#`PjHQ2E#ukcNr$fH|GR(Tv<*1CtB*iD$RD{pD)qa`Ft77$7=3fZLMvj%iFXf7=?L!-;F&kjDUA)?!`=ZlqBFt9y>D0^2lW<3DPz#emY)@%%bI@-!KK^_y1ghP5FB5-4xT@Pt32 zj+B+=;w*<`2Fw?p>S2i*EUfQo#ueTa~wM8+-qiCDrBJr2jQ9Fnu+XDU{;}5h`=YQ{t zvDUdHiILL(3Yz5bvAjUDW>5dQV-6(!tE;8mSw=46=sm<8%TCxqT(A@M_O2kL*78zg z4OHH^Z#fR?kAMJefnz4CNkpV)PKOPjKa8RDawjM5UwL*)BfFxFyP?bZ2J^gX{FX4M zwKqo`!ILpvUncBHtkBkULqVoh*)@eCtD~spOqTW{v5N1Mupw@!%sX5^b2_JvGxxR% z1PSl{7S>I0h)99R;K3;xZ z*h*PSVic62$n)_Uk?IwT^s^y-^ztEzdJKxRBQk+E!r!hPlF)g6B;1X;`h}WLQ%+zH zDooutnnG7pS^b%AOM+${wzki?4bVp_Noc*LTw7Y8=c~?OhC_V4SEyp1&)op!OM$k& z2!9Q4$e_I-1t8lybt^>^ld;aPV5^T(7xYfAD2*uF*s=Rs|Cai%Dmn>a>4Z}Dz$L<; zfn;#}73K8OZyaC=pj+GKH(q8u4l@khgKzo78{SoFrWW+ zMR~{^B|yJh^LF*`rb5N3RSvsNv%u!L?QK5t$_R3`azZeK^33Ekik zTzKT|Dg^bMhaoAlp-3u7(iL^*`itdWHdSM41D%l5lCnBP&6+9ox_$<>{i$fn$u)lz zXb5UJ2i?3~qt0pJ!TK%sKw0)nO|(kgTG>Pxz+--sA&Fp^u05VWQZ4zQxqb-Q{tW`7XBfIw>1M%?5Fg%bdPHPlJh($xYQ zQYssxV~Y0E*Gk3yeMSYPc7R~2O`15Ujr;GS)*!9M+y1+jkFz<-qZ)-VjgE1wEop?3 zn@y;`V}4__yB*h!aT}729rE$z4+%_})6kt-B=ci+<`=swh*-oC4{>r90fN5w>{=bF15rB&q`m14&Z5$Ed{F1Iq(&F@0A?%ILG*)77c?sc+b>;&`umN#*_N z1_VT+;h|$HtK>!?F)Xppn7n@i4TAh8;AP_keL4Q+7aDXqOZQg&w^R{(Hm$3meCQC* z1m#ej=$z=ctwg!X=tzCQ$x>OAk9vLYU!NNmGuHjafto z$Z&NLKhK{Kf|^HglEa(|Cxr6iPK05`^vQd_6VMfv= webpackImagesMap[key] = r(key)); +} + +importAll(imagesContext); diff --git a/docs/specs/app/javascript/packs/client_only.js b/docs/specs/app/javascript/packs/client_only.js new file mode 100644 index 000000000..5eb4a6e48 --- /dev/null +++ b/docs/specs/app/javascript/packs/client_only.js @@ -0,0 +1,6 @@ +//app/javascript/packs/client_only.js +// add any requires for packages that will run client side only +ReactDOM = require('react-dom'); // react-js client side code +jQuery = require('jquery'); // remove if you don't need jQuery +// to add additional NPM packages call run yarn add package-name@version +// then add the require here. diff --git a/docs/specs/app/jobs/application_job.rb b/docs/specs/app/jobs/application_job.rb new file mode 100644 index 000000000..a009ace51 --- /dev/null +++ b/docs/specs/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/docs/specs/app/mailers/application_mailer.rb b/docs/specs/app/mailers/application_mailer.rb new file mode 100644 index 000000000..286b2239d --- /dev/null +++ b/docs/specs/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/docs/specs/app/models/concerns/.keep b/docs/specs/app/models/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/specs/app/models/sample.rb b/docs/specs/app/models/sample.rb new file mode 100644 index 000000000..556d67f79 --- /dev/null +++ b/docs/specs/app/models/sample.rb @@ -0,0 +1,5 @@ +class Sample < ApplicationRecord + def self.super_secret_server_side_method + true + end +end diff --git a/docs/specs/app/policies/hyperstack/application_policy.rb b/docs/specs/app/policies/hyperstack/application_policy.rb new file mode 100644 index 000000000..40dac9d7c --- /dev/null +++ b/docs/specs/app/policies/hyperstack/application_policy.rb @@ -0,0 +1,16 @@ + # /Users/mitch/rubydev/hyperstack/ruby/rails-hyperstack/spec/test_app/app/policies/hyperstack/application_policy.rb + + # Policies regulate access to your public models + # The following policy will open up full access (but only in development) + # The policy system is very flexible and powerful. See the documentation + # for complete details. + module Hyperstack + class ApplicationPolicy + # Allow any session to connect: + always_allow_connection + # Send all attributes from all public models + regulate_all_broadcasts { |policy| policy.send_all } + # Allow all changes to models + allow_change(to: :all, on: [:create, :update, :destroy]) { true } + end unless Rails.env.production? + end diff --git a/docs/specs/app/views/layouts/application.html.erb b/docs/specs/app/views/layouts/application.html.erb new file mode 100644 index 000000000..1003a84ec --- /dev/null +++ b/docs/specs/app/views/layouts/application.html.erb @@ -0,0 +1,15 @@ + + + + TestApp + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + + <%= yield %> + + diff --git a/docs/specs/app/views/layouts/mailer.html.erb b/docs/specs/app/views/layouts/mailer.html.erb new file mode 100644 index 000000000..cbd34d2e9 --- /dev/null +++ b/docs/specs/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/docs/specs/app/views/layouts/mailer.text.erb b/docs/specs/app/views/layouts/mailer.text.erb new file mode 100644 index 000000000..37f0bddbd --- /dev/null +++ b/docs/specs/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/docs/specs/babel.config.js b/docs/specs/babel.config.js new file mode 100644 index 000000000..4df194934 --- /dev/null +++ b/docs/specs/babel.config.js @@ -0,0 +1,70 @@ +module.exports = function(api) { + var validEnv = ['development', 'test', 'production'] + var currentEnv = api.env() + var isDevelopmentEnv = api.env('development') + var isProductionEnv = api.env('production') + var isTestEnv = api.env('test') + + if (!validEnv.includes(currentEnv)) { + throw new Error( + 'Please specify a valid `NODE_ENV` or ' + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: ' + + JSON.stringify(currentEnv) + + '.' + ) + } + + return { + presets: [ + isTestEnv && [ + '@babel/preset-env', + { + targets: { + node: 'current' + } + } + ], + (isProductionEnv || isDevelopmentEnv) && [ + '@babel/preset-env', + { + forceAllTransforms: true, + useBuiltIns: 'entry', + corejs: 3, + modules: false, + exclude: ['transform-typeof-symbol'] + } + ] + ].filter(Boolean), + plugins: [ + 'babel-plugin-macros', + '@babel/plugin-syntax-dynamic-import', + isTestEnv && 'babel-plugin-dynamic-import-node', + '@babel/plugin-transform-destructuring', + [ + '@babel/plugin-proposal-class-properties', + { + loose: true + } + ], + [ + '@babel/plugin-proposal-object-rest-spread', + { + useBuiltIns: true + } + ], + [ + '@babel/plugin-transform-runtime', + { + helpers: false + } + ], + [ + '@babel/plugin-transform-regenerator', + { + async: false + } + ] + ].filter(Boolean) + } +} diff --git a/docs/specs/bin/bundle b/docs/specs/bin/bundle new file mode 100755 index 000000000..f19acf5b5 --- /dev/null +++ b/docs/specs/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +load Gem.bin_path('bundler', 'bundle') diff --git a/docs/specs/bin/rails b/docs/specs/bin/rails new file mode 100755 index 000000000..5badb2fde --- /dev/null +++ b/docs/specs/bin/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/docs/specs/bin/rake b/docs/specs/bin/rake new file mode 100755 index 000000000..d87d5f578 --- /dev/null +++ b/docs/specs/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/docs/specs/bin/setup b/docs/specs/bin/setup new file mode 100755 index 000000000..94fd4d797 --- /dev/null +++ b/docs/specs/bin/setup @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/docs/specs/bin/spring b/docs/specs/bin/spring new file mode 100755 index 000000000..d89ee495f --- /dev/null +++ b/docs/specs/bin/spring @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file loads Spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require 'rubygems' + require 'bundler' + + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == 'spring' } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version + require 'spring/binstub' + end +end diff --git a/docs/specs/bin/update b/docs/specs/bin/update new file mode 100755 index 000000000..58bfaed51 --- /dev/null +++ b/docs/specs/bin/update @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/docs/specs/bin/webpack b/docs/specs/bin/webpack new file mode 100755 index 000000000..1031168d0 --- /dev/null +++ b/docs/specs/bin/webpack @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/webpack_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::WebpackRunner.run(ARGV) +end diff --git a/docs/specs/bin/webpack-dev-server b/docs/specs/bin/webpack-dev-server new file mode 100755 index 000000000..dd9662737 --- /dev/null +++ b/docs/specs/bin/webpack-dev-server @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/dev_server_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::DevServerRunner.run(ARGV) +end diff --git a/docs/specs/bin/yarn b/docs/specs/bin/yarn new file mode 100755 index 000000000..460dd565b --- /dev/null +++ b/docs/specs/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg", *ARGV + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/docs/specs/config.ru b/docs/specs/config.ru new file mode 100644 index 000000000..f7ba0b527 --- /dev/null +++ b/docs/specs/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/docs/specs/config/application.rb b/docs/specs/config/application.rb new file mode 100644 index 000000000..274e98121 --- /dev/null +++ b/docs/specs/config/application.rb @@ -0,0 +1,33 @@ +require_relative 'boot' + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "active_storage/engine" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_view/railtie" +require "action_cable/engine" +require "sprockets/railtie" +# require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module TestApp + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.2 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. + + # Don't generate system test files. + config.generators.system_tests = nil + end +end diff --git a/docs/specs/config/boot.rb b/docs/specs/config/boot.rb new file mode 100644 index 000000000..b9e460cef --- /dev/null +++ b/docs/specs/config/boot.rb @@ -0,0 +1,4 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/docs/specs/config/cable.yml b/docs/specs/config/cable.yml new file mode 100644 index 000000000..26aa82dd9 --- /dev/null +++ b/docs/specs/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: test_app_production diff --git a/docs/specs/config/credentials.yml.enc b/docs/specs/config/credentials.yml.enc new file mode 100644 index 000000000..f2658b4da --- /dev/null +++ b/docs/specs/config/credentials.yml.enc @@ -0,0 +1 @@ +2I4YKMI75zh1i34wXLWZtosTS8RWD7rPvnTklEJUf+LLtLgBPx+x66IgKC1qXwm9YCZKr0RoLKKadRFNV83wbwn9HsYva1+4Y+Uqoe9osebmc6fpj49RFGCiyXn+vPqdwZAcxwV+YgpDXWPuCYGI53DaVe9RR1GA26GrrAUbzHwBWTJnCiBezseWpI/mg1JbhVtNVY08zF7emHZSY8hYAbpPV8jHt4aiiJFvwBpOO9AT5nXp4/q2+L92A6oruJmOg5jOwxT1jyn/Z0IMUUEfranEU6/cLgAiC8B0uVR9xsJf1raTAClnFiXQ4c25RHhyboVTebvEe0MVaEM4sekSIs/sCOZX7FjtVHpPAuzmyAcdsnkiQtGdi0iByyvmVrsg5ipD5Xd2qL3LbD85gMRmUesD7k5ctdjwgBfx--I4i4GlTqE68W2nZv--GCiEYpdDZPAhZ0Tdn5qYQg== \ No newline at end of file diff --git a/docs/specs/config/database.yml b/docs/specs/config/database.yml new file mode 100644 index 000000000..0d02f2498 --- /dev/null +++ b/docs/specs/config/database.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/docs/specs/config/environment.rb b/docs/specs/config/environment.rb new file mode 100644 index 000000000..426333bb4 --- /dev/null +++ b/docs/specs/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/docs/specs/config/environments/development.rb b/docs/specs/config/environments/development.rb new file mode 100644 index 000000000..1311e3e4e --- /dev/null +++ b/docs/specs/config/environments/development.rb @@ -0,0 +1,61 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/docs/specs/config/environments/production.rb b/docs/specs/config/environments/production.rb new file mode 100644 index 000000000..0a7c662b5 --- /dev/null +++ b/docs/specs/config/environments/production.rb @@ -0,0 +1,94 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "test_app_#{Rails.env}" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/docs/specs/config/environments/test.rb b/docs/specs/config/environments/test.rb new file mode 100644 index 000000000..60a779896 --- /dev/null +++ b/docs/specs/config/environments/test.rb @@ -0,0 +1,49 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # added by hyperstack installer + config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s +end diff --git a/docs/specs/config/initializers/application_controller_renderer.rb b/docs/specs/config/initializers/application_controller_renderer.rb new file mode 100644 index 000000000..89d2efab2 --- /dev/null +++ b/docs/specs/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/docs/specs/config/initializers/assets.rb b/docs/specs/config/initializers/assets.rb new file mode 100644 index 000000000..7188478c4 --- /dev/null +++ b/docs/specs/config/initializers/assets.rb @@ -0,0 +1,15 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) + Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s diff --git a/docs/specs/config/initializers/backtrace_silencers.rb b/docs/specs/config/initializers/backtrace_silencers.rb new file mode 100644 index 000000000..59385cdf3 --- /dev/null +++ b/docs/specs/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/docs/specs/config/initializers/content_security_policy.rb b/docs/specs/config/initializers/content_security_policy.rb new file mode 100644 index 000000000..d3bcaa5ec --- /dev/null +++ b/docs/specs/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/docs/specs/config/initializers/cookies_serializer.rb b/docs/specs/config/initializers/cookies_serializer.rb new file mode 100644 index 000000000..5a6a32d37 --- /dev/null +++ b/docs/specs/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/docs/specs/config/initializers/filter_parameter_logging.rb b/docs/specs/config/initializers/filter_parameter_logging.rb new file mode 100644 index 000000000..4a994e1e7 --- /dev/null +++ b/docs/specs/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/docs/specs/config/initializers/hyperstack.rb b/docs/specs/config/initializers/hyperstack.rb new file mode 100644 index 000000000..e3ff05d6d --- /dev/null +++ b/docs/specs/config/initializers/hyperstack.rb @@ -0,0 +1,46 @@ + +# transport controls how push (websocket) communications are +# implemented. The default is :none. +# Other possibilities are :action_cable, :pusher (see www.pusher.com) +# or :simple_poller which is sometimes handy during system debug. + +Hyperstack.transport = :action_cable # :pusher, :simple_poller or :none + + +# Hyperstack.import 'react/react-source-browser' # uncomment this line if you want hyperstack to use its copy of react +Hyperstack.import 'hyperstack/hotloader', client_only: true if Rails.env.development? + +# server_side_auto_require will patch the ActiveSupport Dependencies module +# so that you can define classes and modules with files in both the +# app/hyperstack/xxx and app/xxx directories. For example you can split +# a Todo model into server and client related definitions and place this +# in `app/hyperstack/models/todo.rb`, and place any server only definitions in +# `app/models/todo.rb`. + +require "hyperstack/server_side_auto_require.rb" + +# set the component base class + +Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent' + +# prerendering is default :off, you should wait until your +# application is relatively well debugged before turning on. + +Hyperstack.prerendering = :off # or :on + +# add this line if you need jQuery AND ARE NOT USING WEBPACK +# Hyperstack.import 'hyperstack/component/jquery', client_only: true + +# change definition of on_error to control how errors such as validation +# exceptions are reported on the server +module Hyperstack + def self.on_error(operation, err, params, formatted_error_message) + ::Rails.logger.debug( + "#{formatted_error_message}\n\n" + + Pastel.new.red( + 'To further investigate you may want to add a debugging '\ + 'breakpoint to the on_error method in config/initializers/hyperstack.rb' + ) + ) + end +end if Rails.env.development? diff --git a/docs/specs/config/initializers/inflections.rb b/docs/specs/config/initializers/inflections.rb new file mode 100644 index 000000000..ac033bf9d --- /dev/null +++ b/docs/specs/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/docs/specs/config/initializers/mime_types.rb b/docs/specs/config/initializers/mime_types.rb new file mode 100644 index 000000000..dc1899682 --- /dev/null +++ b/docs/specs/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/docs/specs/config/initializers/wrap_parameters.rb b/docs/specs/config/initializers/wrap_parameters.rb new file mode 100644 index 000000000..bbfc3961b --- /dev/null +++ b/docs/specs/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/docs/specs/config/locales/en.yml b/docs/specs/config/locales/en.yml new file mode 100644 index 000000000..decc5a857 --- /dev/null +++ b/docs/specs/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/docs/specs/config/master.key b/docs/specs/config/master.key new file mode 100644 index 000000000..a2b9f71e1 --- /dev/null +++ b/docs/specs/config/master.key @@ -0,0 +1 @@ +814e5877df4bd7952ab20a37feca8b00 \ No newline at end of file diff --git a/docs/specs/config/puma.rb b/docs/specs/config/puma.rb new file mode 100644 index 000000000..b2102072b --- /dev/null +++ b/docs/specs/config/puma.rb @@ -0,0 +1,37 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/docs/specs/config/routes.rb b/docs/specs/config/routes.rb new file mode 100644 index 000000000..115e3b498 --- /dev/null +++ b/docs/specs/config/routes.rb @@ -0,0 +1,6 @@ +Rails.application.routes.draw do + mount Hyperstack::Engine => '/hyperstack' # this route should be first in the routes file so it always matches + get '/(*others)', to: 'hyperstack#App' + # get '/(*others)', to: 'hyperstack#app' + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end diff --git a/docs/specs/config/spring.rb b/docs/specs/config/spring.rb new file mode 100644 index 000000000..9fa7863f9 --- /dev/null +++ b/docs/specs/config/spring.rb @@ -0,0 +1,6 @@ +%w[ + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +].each { |path| Spring.watch(path) } diff --git a/docs/specs/config/storage.yml b/docs/specs/config/storage.yml new file mode 100644 index 000000000..d32f76e8f --- /dev/null +++ b/docs/specs/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/docs/specs/config/webpack/development.js b/docs/specs/config/webpack/development.js new file mode 100644 index 000000000..c5edff94a --- /dev/null +++ b/docs/specs/config/webpack/development.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/docs/specs/config/webpack/environment.js b/docs/specs/config/webpack/environment.js new file mode 100644 index 000000000..d16d9af74 --- /dev/null +++ b/docs/specs/config/webpack/environment.js @@ -0,0 +1,3 @@ +const { environment } = require('@rails/webpacker') + +module.exports = environment diff --git a/docs/specs/config/webpack/production.js b/docs/specs/config/webpack/production.js new file mode 100644 index 000000000..be0f53aac --- /dev/null +++ b/docs/specs/config/webpack/production.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'production' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/docs/specs/config/webpack/test.js b/docs/specs/config/webpack/test.js new file mode 100644 index 000000000..c5edff94a --- /dev/null +++ b/docs/specs/config/webpack/test.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/docs/specs/config/webpacker.yml b/docs/specs/config/webpacker.yml new file mode 100644 index 000000000..a6b146566 --- /dev/null +++ b/docs/specs/config/webpacker.yml @@ -0,0 +1,92 @@ +# Note: You must restart bin/webpack-dev-server for changes to take effect + +default: &default + source_path: app/javascript + source_entry_path: packs + public_root_path: public + public_output_path: packs + cache_path: tmp/cache/webpacker + webpack_compile_output: true + + # Additional paths webpack should lookup modules + # ['app/assets', 'engine/foo/app/assets'] + additional_paths: [] + + # Reload manifest.json on all requests so we reload latest compiled packs + cache_manifest: false + + # Extract and emit a css file + extract_css: false + + static_assets_extensions: + - .jpg + - .jpeg + - .png + - .gif + - .tiff + - .ico + - .svg + - .eot + - .otf + - .ttf + - .woff + - .woff2 + + extensions: + - .mjs + - .js + - .sass + - .scss + - .css + - .module.sass + - .module.scss + - .module.css + - .png + - .svg + - .gif + - .jpeg + - .jpg + +development: + <<: *default + compile: true + + # Reference: https://webpack.js.org/configuration/dev-server/ + dev_server: + https: false + host: localhost + port: 3035 + public: localhost:3035 + hmr: false + # Inline should be set to true if using HMR + inline: true + overlay: true + compress: true + disable_host_check: true + use_local_ip: false + quiet: false + pretty: false + headers: + 'Access-Control-Allow-Origin': '*' + watch_options: + ignored: '**/node_modules/**' + + +test: + <<: *default + compile: true + + # Compile test packs to a separate directory + public_output_path: packs-test + +production: + <<: *default + + # Production depends on precompilation of packs prior to booting for performance. + compile: false + + # Extract and emit a css file + extract_css: true + + # Cache manifest.json for performance + cache_manifest: true diff --git a/docs/specs/db/development.sqlite3 b/docs/specs/db/development.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..d0ada1f15c36b19e80301e40aed23bd438787efd GIT binary patch literal 36864 zcmeI*J#X7a7zc3DZ;~C`O4>ngUQkwEqNbF1mu6lj^^<8L{rTbMrGT7kHtU(?W3=$`!xeY6(BAw_BA&L;2|Y zR4VJb>JL}OJ8`r_vIB|I^wi)J8=Wd}f6tS_Hgej5*Okj%8GcV%%Gi@deph&*WcI?G zCc7%adQ-?P#bKx36}|D|uBge)}P=A*mwsqD3D>I*Sw(`$AbaT@d=7Dn30y(v8vQT&8Sj6n4KZoLza@6nJ? z07~Y^ekW!d1svArqpjIgmNE6k=lz~|-AbH%-Imvoi5+@za!o3m8|~wyP2vte70-v^ z51%uc)^a2dyvF`WCnt^gaA�e|PBa+(Py{rYiMTAP(OQptm2k`|FiSfmX<*()mL* zl`R(4=;Of<`+K74RhsoDT`%VE^~MWkMoEs<#~ugr*pbZdVYiYd`SQ#{cBeS43&$F+ zkYh!&@-Ul@549_DRx^Jo@&^+HAOHafKmY;|fB*y_009U<00QrYz-4tNZyF{sEW!zA zgpeiKFP#6s7lVsVKmY;|fB*y_009U<00Izz00iC$T$#-;j-LMyKL3BIWL^RZ0uX=z z1Rwwb2tWV=5P$##AOL|2BJe<+UtcemMeCs6ZMT}D6$au!G}@hbF+Q``4NE6nBfP{& ziO_;W9g7&#YX+bHzg9A@FGvp&1_U4g0SG_<0uX=z1Rwwb2tWV=XBSwVU0+Q6`5^xM zUw-KyCI~`Q@!-B;y3L#_s|scsL5=yY z#cICI0;^gjHs_u}si<0x&!{c}E_BMOMCa97P3MMVx{R1qIMovy8D4GaYQ!i}r^Kv+ z&1}~-$7*=Vh-)0fGNQHXY325@xXYbRqwe=_D0PmZF+#1$p)tZ~#IBu%lyl2)YRuI+ z^9`K_)X=N8XX@3OQ>7;JX<(YCB4w0QHjy;0;a0SE^#sy$49(_jA}OObcO~hEXCd{7 zu&Gkj<8MkPFZRYnW81;6dc!}GSlB)N9<~G4J{di8g52w zADlpXexY&8W^`g`+;R*Sv5(F|8gOcG!)N_hx=syO=*ke**?#Ny`U7m%!RNW?N{A(aQzz_eUXCaBFMY0Z>}0E_5y z&9rTUF~{QXLK^3ySu$zCa2;aUW2s=4tU(PW(c0<>q~{o#X;D5oG?SY4&PqzzSIWw% z-kgTb4zBOFOT;X2YTLHO=Tge;@n#e69bk7`2N3FO6JuCe`7~@5P$## zAOHafKmY;|fB*y_009V$2z;u}DZ6Q1*R{Pc?39YdM%(uqd+lCWVuoQ9kNy>)xb!cb C-_b7s literal 0 HcmV?d00001 diff --git a/docs/specs/db/migrate/20210318185111_create_samples.rb b/docs/specs/db/migrate/20210318185111_create_samples.rb new file mode 100644 index 000000000..414b4f664 --- /dev/null +++ b/docs/specs/db/migrate/20210318185111_create_samples.rb @@ -0,0 +1,10 @@ +class CreateSamples < ActiveRecord::Migration[5.2] + def change + create_table :samples do |t| + t.string :name + t.text :description + + t.timestamps + end + end +end diff --git a/docs/specs/db/schema.rb b/docs/specs/db/schema.rb new file mode 100644 index 000000000..9b567d04c --- /dev/null +++ b/docs/specs/db/schema.rb @@ -0,0 +1,22 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2021_03_18_185111) do + + create_table "samples", force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + +end diff --git a/docs/specs/db/seeds.rb b/docs/specs/db/seeds.rb new file mode 100644 index 000000000..1beea2acc --- /dev/null +++ b/docs/specs/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first) diff --git a/docs/specs/db/test.sqlite3 b/docs/specs/db/test.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..b8bc41c179da04a1c709f9053da5e8b458797b04 GIT binary patch literal 28672 zcmeI%J#X4T7zgk(uT3Q&9V&(lIXO#EUf@EVqAaajp)?91DTM)bu&!`TtYSm#1JRLu zg0B4{`6!(_cCM6f&~tbx6quq)5&B#B<(~7|ety2?!5`k2Y$@pHw&!q3^JJZ98hK4A zAtb8SxLU_cSRF*h4Yk*%j+Y%q$^PlLMDizzC88vCnEaL8Ony&96F-&>KpX-PfB*y_ z009U<00Izzz<(7Ol%w&qVQ4q6B>&VB+_P*~3eV*&%Mp?{x#WvtvA30?SuN74`KDB) z`l3^Gx~+c^Uv=8!UgL^;w0vHriJ1o4Ld8A)$|xm5%rdX8%~^5yD9vJXLka7 zol4P{51uKB0BAkfV1WPxAOHafKmY;|fB*y_009U