diff --git a/lib/warped/emails/slottable.rb b/lib/warped/emails/slottable.rb
index 46324b6..d85f470 100644
--- a/lib/warped/emails/slottable.rb
+++ b/lib/warped/emails/slottable.rb
@@ -9,7 +9,7 @@ module Slottable
extend ActiveSupport::Concern
included do
- class_attribute :slots, default: { one: {}, many: {} }
+ class_attribute :slots, default: { one: {}, many: {} }, instance_accessor: false
end
class_methods do
@@ -57,6 +57,10 @@ def #{name}
RUBY
end
end
+
+ def slots
+ @slots ||= self.class.slots.deep_dup
+ end
end
end
end
diff --git a/spec/warped/emails/components/base_spec.rb b/spec/warped/emails/components/base_spec.rb
new file mode 100644
index 0000000..db40570
--- /dev/null
+++ b/spec/warped/emails/components/base_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+RSpec.describe Warped::Emails::Base do
+ let(:base) { Warped::Emails::Base.new }
+
+ describe "#template" do
+ it "raises NotImplementedError" do
+ expect { base.template }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe "#content" do
+ context "when the content block is not set" do
+ it "returns nil" do
+ expect(base.content).to be_nil
+ end
+ end
+
+ context "when the content block is set" do
+ before { base.instance_variable_set(:@content_block, "
Sample Content
") }
+
+ it "returns the content block" do
+ expect(base.content).to eq("Sample Content
")
+ end
+ end
+ end
+
+ describe "#helpers" do
+ context "when the view context is not set" do
+ it "raises ArgumentError" do
+ expect do
+ base.helpers
+ end.to raise_error(ArgumentError,
+ "helpers cannot be used during initialization, as it depends on the view context")
+ end
+ end
+
+ context "when the view context is set" do
+ let(:view_context) { double("view_context") }
+ before { base.instance_variable_set(:@view_context, view_context) }
+
+ it "returns the view context" do
+ expect(base.helpers).to eq(view_context)
+ end
+ end
+ end
+
+ describe "#render_in" do
+ let(:view_context) { double("view_context") }
+
+ before do
+ allow(view_context).to receive(:content_tag).with(:h1, "Sample Content").and_return("Sample Content
")
+ allow(view_context).to receive(:capture).and_yield
+ allow(base).to receive(:template).and_return("Template")
+ end
+
+ subject { base.render_in(view_context) { |base| base.content_tag(:h1, "Sample Content") } }
+
+ it "sets the view context" do
+ subject
+ expect(base.view_context).to eq(view_context)
+ end
+
+ it "sets the content block" do
+ expect(subject).to eq("Template")
+ expect(base.content).to eq("Sample Content
")
+ end
+ end
+end
diff --git a/spec/warped/emails/components/valid_components_spec.rb b/spec/warped/emails/components/valid_components_spec.rb
new file mode 100644
index 0000000..9c2b4fe
--- /dev/null
+++ b/spec/warped/emails/components/valid_components_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+def components
+ %w[
+ Warped::Emails::Align
+ Warped::Emails::Button
+ Warped::Emails::Divider
+ Warped::Emails::Heading
+ Warped::Emails::Link
+ Warped::Emails::Layouts::Main
+ Warped::Emails::Spacer
+ Warped::Emails::Text
+ ]
+end
+
+RSpec.describe "Warped::Emails Components classes are valid" do
+ components.each do |component|
+ it "#{component} is valid" do
+ expect { Object.const_get(component) }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/warped/emails/slottable_spec.rb b/spec/warped/emails/slottable_spec.rb
new file mode 100644
index 0000000..da59e5b
--- /dev/null
+++ b/spec/warped/emails/slottable_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require "action_view"
+
+RSpec.describe Warped::Emails::Slottable do
+ let(:test_class) do
+ Class.new(Warped::Emails::Base) do
+ include Warped::Emails::Slottable
+ end
+ end
+
+ it "adds the slots class attribute" do
+ expect(test_class.slots).to eq(one: {}, many: {})
+ expect(test_class.new.slots).to eq(one: {}, many: {})
+ end
+
+ it "adds the slots_one class method" do
+ expect(test_class).to respond_to(:slots_one)
+ end
+
+ it "adds the slots_many class method" do
+ expect(test_class).to respond_to(:slots_many)
+ end
+
+ describe ".slots_one" do
+ before { test_class.slots_one(:header) }
+
+ it "adds the with_header method" do
+ component = test_class.new
+ expect(component).to respond_to(:with_header)
+ expect do
+ component.with_header do
+ "Header
"
+ end
+ end.to change { component.slots[:one][:header] }.from(nil).to(be_a(Proc))
+ end
+
+ it "adds the header method" do
+ component = test_class.new
+ component.instance_variable_set(:@view_context, ActionView::Base.new([], {}, nil))
+
+ expect(component).to respond_to(:header)
+
+ component.with_header { "Header
" }
+
+ expect(component.header).to eq("<h1>Header</h1>")
+ end
+ end
+
+ describe ".slots_many" do
+ before { test_class.slots_many(:headers) }
+
+ it "adds the with_headers method" do
+ component = test_class.new
+ expect(component).to respond_to(:with_header)
+ expect do
+ component.with_header do
+ "Header
"
+ end
+ end.to change { component.slots[:many][:headers] }.from(nil).to([be_a(Proc)])
+ end
+
+ it "adds the headers method" do
+ component = test_class.new
+ component.instance_variable_set(:@view_context, ActionView::Base.new([], {}, nil))
+
+ expect(component).to respond_to(:headers)
+
+ component.with_header { "Header1
" }
+ component.with_header { "Header2
" }
+
+ expect(component.headers).to eq(["<h1>Header1</h1>", "<h1>Header2</h1>"])
+ end
+ end
+end
diff --git a/spec/warped/emails/styleable_spec.rb b/spec/warped/emails/styleable_spec.rb
new file mode 100644
index 0000000..be2665b
--- /dev/null
+++ b/spec/warped/emails/styleable_spec.rb
@@ -0,0 +1,226 @@
+# frozen_string_literal: true
+
+require "debug"
+RSpec.describe Warped::Emails::Styleable do
+ let(:test_class) do
+ Class.new(Warped::Emails::Base) do
+ include Warped::Emails::Styleable
+ end
+ end
+
+ describe ".default_variants" do
+ subject { test_class.default_variants }
+
+ context "when the default_variants is not set" do
+ it "returns an empty hash" do
+ is_expected.to eq({})
+ end
+
+ it "sets the @default_variants" do
+ expect { subject }.to change { test_class.instance_variable_get(:@default_variants) }.from(nil).to({})
+ end
+ end
+
+ context "when the default_variants is set" do
+ before { test_class.instance_variable_set(:@default_variants, { size: "sm" }) }
+
+ it "returns the default_variants" do
+ is_expected.to eq(size: "sm")
+ end
+
+ it "does not change the @default_variants" do
+ expect { subject }.not_to(change { test_class.instance_variable_get(:@default_variants) })
+ end
+ end
+ end
+
+ describe ".base_styles" do
+ subject { test_class.base_styles }
+
+ context "when the base_styles is not set" do
+ it "returns an empty hash" do
+ is_expected.to eq({})
+ end
+
+ it "sets the @base_styles" do
+ expect { subject }.to change { test_class.instance_variable_get(:@base_styles) }.from(nil).to({})
+ end
+ end
+
+ context "when the base_styles is set" do
+ before { test_class.instance_variable_set(:@base_styles, { color: "red" }) }
+
+ it "returns the base_styles" do
+ is_expected.to eq(color: "red")
+ end
+
+ it "does not change the @base_styles" do
+ expect { subject }.not_to(change { test_class.instance_variable_get(:@base_styles) })
+ end
+ end
+ end
+
+ describe ".variants" do
+ subject { test_class.variants }
+
+ context "when the variants is not set" do
+ it "returns an empty hash" do
+ is_expected.to eq({})
+ end
+
+ it "sets the @variants" do
+ expect { subject }.to change { test_class.instance_variable_get(:@variants) }.from(nil).to({})
+ end
+ end
+
+ context "when the variants is set" do
+ before { test_class.instance_variable_set(:@variants, { size: "sm" }) }
+
+ it "returns the variants" do
+ is_expected.to eq(size: "sm")
+ end
+
+ it "does not change the @variants" do
+ expect { subject }.not_to(change { test_class.instance_variable_get(:@variants) })
+ end
+ end
+ end
+
+ describe ".variant" do
+ context "when the block is not given" do
+ it "raises ArgumentError" do
+ expect do
+ test_class.variant
+ end.to raise_error(ArgumentError, "You must provide a block")
+ end
+ end
+
+ context "when the block is given" do
+ context "when the variant_name is not given" do
+ it "uses the default variant name" do
+ test_class.variant do
+ color do
+ red { "color: red" }
+ end
+ end
+
+ expect(test_class.variants[:_base_variant][:color][:red].class).to eq(Proc)
+ expect(test_class.variants[:_base_variant][:color][:red].call).to eq("color: red")
+ end
+ end
+
+ context "when the variant_name is given" do
+ it "uses the given variant name" do
+ test_class.variant(:highlight) do
+ color do
+ red { "color: red" }
+ end
+ end
+
+ expect(test_class.variants[:highlight][:color][:red].class).to eq(Proc)
+ expect(test_class.variants[:highlight][:color][:red].call).to eq("color: red")
+ end
+ end
+ end
+ end
+
+ describe ".default_variant" do
+ before do
+ test_class.variant do
+ color do
+ red { "color: red" }
+ end
+ end
+
+ test_class.variant(:highlight) do
+ color do
+ red { "color: blue" }
+ end
+ end
+ end
+
+ context "when the name is not given" do
+ it "uses the default variant name" do
+ expect { test_class.default_variant(color: :red) }.to change {
+ test_class.default_variants.dig(:_base_variant, :color)
+ }.from(nil).to(:red)
+ end
+ end
+
+ context "when the name is given" do
+ it "uses the given variant name" do
+ expect { test_class.default_variant(:highlight, color: :red) }.to change {
+ test_class.default_variants
+ .dig(:highlight, :color)
+ }.from(nil).to(:red)
+ end
+ end
+ end
+
+ describe "#default_variants" do
+ it "returns the class default_variants" do
+ expect(test_class.new.default_variants).to eq(test_class.default_variants)
+ end
+ end
+
+ describe "#base_styles" do
+ it "returns the class base_styles" do
+ expect(test_class.new.base_styles).to eq(test_class.base_styles)
+ end
+ end
+
+ describe "#variants" do
+ it "returns the class variants" do
+ expect(test_class.new.variants).to eq(test_class.variants)
+ end
+ end
+
+ describe "#style" do
+ before do
+ test_class.variant do
+ base { ["color: red"] }
+ end
+ end
+ context "when the variant_name is not given" do
+ context "when the kwargs is not given" do
+ context "when the default_variants is not set" do
+ it "returns the base_styles" do
+ expect(test_class.new.style).to eq("color: red")
+ end
+ end
+
+ context "when the default_variants is set" do
+ before do
+ test_class.variant do
+ size do
+ sm { "font-size: 12px" }
+ end
+ end
+
+ test_class.default_variant(size: :sm)
+ end
+
+ it "returns the base_styles appended with the default_variants" do
+ expect(test_class.new.style).to eq("color: red; font-size: 12px")
+ end
+ end
+ end
+
+ context "when the kwargs is given" do
+ before do
+ test_class.variant do
+ size do
+ sm { "font-size: 12px" }
+ md { "font-size: 114px" }
+ end
+ end
+ test_class.default_variant(size: :sm)
+ end
+
+ it "returns the base_styles appended with the default_variants, overriden by the kwargs" do
+ expect(test_class.new.style(size: :md)).to eq("color: red; font-size: 114px")
+ end
+ end
+ end
+ end
+end