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