diff --git a/lib/unitsml/unitsdb/database.rb b/lib/unitsml/unitsdb/database.rb index 7a5be53..e0ff20b 100644 --- a/lib/unitsml/unitsdb/database.rb +++ b/lib/unitsml/unitsdb/database.rb @@ -3,18 +3,29 @@ module Unitsml module Unitsdb class Database < ::Unitsdb::Database - DATABASE = nil + def self.load_opal_payload(payload) + @opal_payload = payload + end def self.from_db(dir_path, context: Unitsml::Configuration.context.id) return super unless RUBY_ENGINE == "opal" context_id = context.to_sym - raise Unitsml::Errors::OpalPayloadNotBundledError unless DATABASE + raise Unitsml::Errors::OpalPayloadNotBundledError unless opal_payload + # Ensure the UnitsML context is registered when context: is direct. Unitsml::Configuration.context - from_hash(DATABASE, register: context_id) + from_hash(opal_payload, register: context_id) + end + + def self.opal_payload + return @opal_payload if instance_variable_defined?(:@opal_payload) + return unless const_defined?(:DATABASE, false) + + @opal_payload = const_get(:DATABASE, false) end + private_class_method :opal_payload Configuration.register_model(self, id: :database) end diff --git a/spec/unitsml/unitsdb/database_spec.rb b/spec/unitsml/unitsdb/database_spec.rb index cfe495f..10b7036 100644 --- a/spec/unitsml/unitsdb/database_spec.rb +++ b/spec/unitsml/unitsdb/database_spec.rb @@ -21,7 +21,29 @@ end context "when running on opal" do - before { stub_const("RUBY_ENGINE", "opal") } + def clear_opal_payload + return unless described_class.instance_variable_defined?(:@opal_payload) + + described_class.remove_instance_variable(:@opal_payload) + end + + before do + stub_const("RUBY_ENGINE", "opal") + clear_opal_payload + end + + after do + clear_opal_payload + end + + def allow_from_hash(database_class) + allow(database_class).to receive(:from_hash).and_return(:database) + end + + def expect_loaded_payload(database_class, payload) + expect(database_class).to have_received(:from_hash) + .with(payload, register: :unitsml_ruby) + end it "raises a clear error when the bundled payload is missing" do expect do @@ -29,6 +51,27 @@ end.to raise_error(Unitsml::Errors::OpalPayloadNotBundledError, /not bundled/) end + + it "loads the bundled Opal payload" do + payload = { "units" => [] } + described_class.load_opal_payload(payload) + allow_from_hash(described_class) + + expect(described_class.from_db("/does/not/matter", + context: :unitsml_ruby)).to eq(:database) + expect_loaded_payload(described_class, payload) + end + + it "falls back to a legacy DATABASE constant" do + payload = { "units" => [:from_const] } + subclass = Class.new(described_class) + subclass.const_set(:DATABASE, payload) + allow_from_hash(subclass) + + expect(subclass.from_db("/does/not/matter", + context: :unitsml_ruby)).to eq(:database) + expect_loaded_payload(subclass, payload) + end end end @@ -58,6 +101,49 @@ end end + context "when running on opal with a loaded payload" do + let(:payload) { { "units" => [] } } + + before do + stub_const("RUBY_ENGINE", "opal") + allow(Unitsml::Configuration).to receive(:context).and_call_original + clear_unitsdb_database_cache + clear_unitsml_database_cache + Unitsml::Configuration.context(force_populate: true) + Unitsml::Unitsdb::Database.load_opal_payload(payload) + allow(Unitsml::Unitsdb::Database) + .to receive(:from_hash) + .and_return(:opal_database) + end + + after do + clear_unitsdb_database_cache + clear_unitsml_database_cache + clear_opal_payload + end + + def clear_unitsdb_database_cache + Unitsdb.instance_variable_set(:@databases, nil) + end + + def clear_unitsml_database_cache + described_class.instance_variable_set(:@database, nil) + end + + def clear_opal_payload + database_class = Unitsml::Unitsdb::Database + return unless database_class.instance_variable_defined?(:@opal_payload) + + database_class.remove_instance_variable(:@opal_payload) + end + + it "loads the UnitsML database through the real unitsdb-ruby loader" do + expect(described_class.database).to eq(:opal_database) + expect(Unitsml::Unitsdb::Database).to have_received(:from_hash) + .with(payload, register: :unitsml_ruby) + end + end + context "when unitsdb-ruby exposes a database loader" do before do stub_const("RUBY_ENGINE", "ruby") diff --git a/spec/unitsml_spec.rb b/spec/unitsml_spec.rb index 054263e..bc99558 100644 --- a/spec/unitsml_spec.rb +++ b/spec/unitsml_spec.rb @@ -1,10 +1,36 @@ # frozen_string_literal: true +require "open3" +require "rbconfig" + RSpec.describe Unitsml do it "has a version number" do expect(Unitsml::VERSION).not_to be_nil end + it "does not change an existing global XML adapter when required" do + lib_path = File.expand_path("../lib", __dir__) + script = <<~RUBY + require "lutaml/model" + Lutaml::Model::Config.xml_adapter_type = :nokogiri + require "unitsml" + + unless Lutaml::Model::Config.xml_adapter_type == :nokogiri + abort "expected :nokogiri, got \#{Lutaml::Model::Config.xml_adapter_type.inspect}" + end + RUBY + + _stdout, stderr, status = Open3.capture3( + RbConfig.ruby, + "-I", + lib_path, + "-e", + script, + ) + + expect(status).to be_success, stderr + end + it "parses a basic unit expression" do formula = described_class.parse("mm") expect(formula).to be_a(Unitsml::Formula)