diff --git a/app/assets/javascripts/alchemy/admin.js b/app/assets/javascripts/alchemy/admin.js
index f7cdfb5eb8..597bfed13d 100644
--- a/app/assets/javascripts/alchemy/admin.js
+++ b/app/assets/javascripts/alchemy/admin.js
@@ -8,7 +8,6 @@
//= require alchemy/alchemy.confirm_dialog
//= require alchemy/alchemy.elements_window
//= require alchemy/alchemy.fixed_elements
-//= require alchemy/alchemy.growler
//= require alchemy/alchemy.image_overlay
//= require alchemy/alchemy.link_dialog
//= require alchemy/alchemy.preview_window
diff --git a/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee
index ba1116649d..3d9fbfe62a 100644
--- a/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee
+++ b/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee
@@ -136,10 +136,10 @@ class window.Alchemy.Dialog
else
error_header = "#{xhr.statusText} (#{xhr.status})"
error_body = "Please check log and try again."
- $errorDiv = $("
">
+
<% flash.keys.each do |flash_type| %>
- <%= render_flash_notice(flash[flash_type.to_sym], flash_type) if flash[flash_type.to_sym].present? %>
+ <% if flash[flash_type.to_sym].present? %>
+ <%= render_flash_notice(flash[flash_type.to_sym], flash_type) %>
+ <% end %>
<% end %>
diff --git a/app/views/alchemy/admin/styleguide/index.html.erb b/app/views/alchemy/admin/styleguide/index.html.erb
index 4ccbf7bc16..0c9fd4b87f 100644
--- a/app/views/alchemy/admin/styleguide/index.html.erb
+++ b/app/views/alchemy/admin/styleguide/index.html.erb
@@ -38,13 +38,13 @@
Info Message
<%= render_message do %>
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<% end %>
Warning Message
<%= render_message :warning do %>
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<% end %>
Error Message
@@ -53,27 +53,32 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<% end %>
+
Growl Messages
+
+<%= link_to render_icon(:check), 'javascript:Alchemy.growl("Success message")', class: "icon_button" %>
+<%= link_to render_icon(:info), 'javascript:Alchemy.growl("Info message", "info")', class: "icon_button" %>
+<%= link_to render_icon(:alert), 'javascript:Alchemy.growl("Warning message", "warn")', class: "icon_button" %>
+<%= link_to render_icon(:bug), 'javascript:Alchemy.growl("Error message", "error")', class: "icon_button" %>
+
Dialog Links
-
+
+
+ <%= render_icon "window" %>
+
Forms
diff --git a/app/views/alchemy/base/error_notice.html.erb b/app/views/alchemy/base/error_notice.html.erb
index 649c79e1db..52b0cf7df7 100644
--- a/app/views/alchemy/base/error_notice.html.erb
+++ b/app/views/alchemy/base/error_notice.html.erb
@@ -1 +1,3 @@
-<%= render_flash_notice @notice, :error %>
+<%= render_message :error do %>
+
<%= @notice %>
+<% end %>
diff --git a/spec/components/alchemy/admin/message_spec.rb b/spec/components/alchemy/admin/message_spec.rb
new file mode 100644
index 0000000000..55a67bab09
--- /dev/null
+++ b/spec/components/alchemy/admin/message_spec.rb
@@ -0,0 +1,55 @@
+require "rails_helper"
+
+RSpec.describe Alchemy::Admin::Message, type: :component do
+ before do
+ render
+ end
+
+ subject(:render) do
+ render_inline described_class.new(message)
+ end
+
+ let(:message) { "This is a message" }
+
+ it "renders an alchemy-message with default type" do
+ expect(page).to have_css('alchemy-message[type="info"]', text: "This is a message")
+ end
+
+ it "renders a not-dismissable alchemy-message by default" do
+ expect(page).to_not have_css "alchemy-message[dismissable]"
+ end
+
+ context "with message given as block" do
+ subject(:render) do
+ render_inline described_class.new do
+ "
This is a block message
".html_safe
+ end
+ end
+
+ it "renders an alchemy-message with default type" do
+ expect(page).to have_css('alchemy-message[type="info"] > p', text: "This is a block message")
+ end
+ end
+
+ context "with type given" do
+ subject(:render) do
+ render_inline described_class.new(message, type: type)
+ end
+
+ let(:type) { "alert" }
+
+ it "renders an alchemy-message with given type" do
+ expect(page).to have_css 'alchemy-message[type="alert"]'
+ end
+ end
+
+ context "with dismissable set to true" do
+ subject(:render) do
+ render_inline described_class.new(message, dismissable: true)
+ end
+
+ it "renders an dismissable alchemy-message" do
+ expect(page).to have_css "alchemy-message[dismissable]"
+ end
+ end
+end
diff --git a/spec/features/admin/nodes_management_spec.rb b/spec/features/admin/nodes_management_spec.rb
index 14ccb86a19..83c6d932a7 100644
--- a/spec/features/admin/nodes_management_spec.rb
+++ b/spec/features/admin/nodes_management_spec.rb
@@ -41,7 +41,7 @@ def add_menu_item
open_page_properties
click_button "Add a menu node"
- within ".flash.error" do
+ within "alchemy-message[type='error']" do
expect(page).to have_content("Menu Type can't be blank")
end
end
diff --git a/spec/helpers/alchemy/base_helper_spec.rb b/spec/helpers/alchemy/base_helper_spec.rb
index db9db94884..c08e751aa7 100644
--- a/spec/helpers/alchemy/base_helper_spec.rb
+++ b/spec/helpers/alchemy/base_helper_spec.rb
@@ -24,18 +24,44 @@ module Alchemy
describe "#render_message" do
context "if no argument is passed" do
- it "should render a div with an info icon and the given content" do
- expect(helper.render_message { content_tag(:p, "my notice") }).to match(
- /
<\/alchemy-icon>my notice/
- )
+ it "should render an alchemy-message with an info icon and the given content" do
+ expect(helper.render_message { content_tag(:p, "my notice") }).to eq <<~HTML
+
+ my notice
+
+ HTML
end
end
context "if an argument is passed" do
- it "should render the passed argument as the css classname for the icon container" do
- expect(helper.render_message(:error) { content_tag(:p, "my notice") }).to match(
- /
/
- )
+ it "should render the passed argument as the type for the message" do
+ expect(helper.render_message(:error) { content_tag(:p, "my notice") }).to eq <<~HTML
+
+ my notice
+
+ HTML
+ end
+ end
+ end
+
+ describe "#render_flash_notice" do
+ context "if no argument is passed" do
+ it "should render an alchemy-message with an check icon and the given content" do
+ expect(helper.render_flash_notice("my notice")).to eq <<~HTML
+
+ my notice
+
+ HTML
+ end
+ end
+
+ context "if an argument is passed" do
+ it "should render the passed argument as the type for the message" do
+ expect(helper.render_flash_notice("A error", :error)).to eq <<~HTML
+
+ A error
+
+ HTML
end
end
end
@@ -63,43 +89,5 @@ module Alchemy
end
end
end
-
- describe "#message_icon_class" do
- subject { helper.message_icon_class(message_type) }
-
- context "when `warning`, `warn` or `alert` message type is given" do
- %w[warning warn alert].each do |type|
- let(:message_type) { type }
-
- it { is_expected.to eq "exclamation" }
- end
- end
-
- context "when `notice` message type is given" do
- let(:message_type) { "notice" }
-
- it { is_expected.to eq "check" }
- end
-
- context "when `hint` message type is given" do
- let(:message_type) { "hint" }
-
- it { is_expected.to eq "info" }
- end
-
- context "when `error` message type is given" do
- let(:message_type) { "error" }
-
- it { is_expected.to eq "bug" }
- end
-
- context "when unknown message type is given" do
- let(:message_type) { "info" }
-
- it "returns the given message type as icon name" do
- is_expected.to eq "info"
- end
- end
- end
end
end
diff --git a/spec/javascript/alchemy_admin/components/message.spec.js b/spec/javascript/alchemy_admin/components/message.spec.js
new file mode 100644
index 0000000000..43caea0770
--- /dev/null
+++ b/spec/javascript/alchemy_admin/components/message.spec.js
@@ -0,0 +1,216 @@
+import "alchemy_admin/components/message"
+import { renderComponent } from "./component.helper"
+
+describe("alchemy-message", () => {
+ describe("dismiss", () => {
+ describe("when dismissable", () => {
+ it("dismisses on click", () => {
+ const html = `
+
+ A message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ const spy = jest.spyOn(component, "dismiss")
+ component.dispatchEvent(new Event("click"))
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it("dismisses after delay", () => {
+ return new Promise((resolve) => {
+ const html = `
+
+ A message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ const spy = jest.spyOn(component, "dismiss")
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ resolve()
+ }, 15)
+ })
+ }, 100)
+
+ it("when type error, does not dismis after delay", () => {
+ return new Promise((resolve) => {
+ const html = `
+
+ A message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ const spy = jest.spyOn(component, "dismiss")
+ setTimeout(() => {
+ expect(spy).not.toHaveBeenCalled()
+ resolve()
+ }, 15)
+ })
+ }, 100)
+ })
+
+ describe("when type error", () => {
+ it("dismisses on click", () => {
+ const html = `
+
+ A message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ const spy = jest.spyOn(component, "dismiss")
+ component.dispatchEvent(new Event("click"))
+ expect(spy).toHaveBeenCalled()
+ })
+ })
+
+ describe("when not type error nor dismissable", () => {
+ it("dismisses on click", () => {
+ const html = `
+
+ A message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ const spy = jest.spyOn(component, "dismiss")
+ component.dispatchEvent(new Event("click"))
+ expect(spy).not.toHaveBeenCalled()
+ })
+ })
+ })
+
+ describe("type", () => {
+ describe("when message type is given", () => {
+ it("is given type", () => {
+ const html = `
+
+ A warning message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ expect(component.type).toEqual("warning")
+ })
+ })
+
+ describe("when message type is not given", () => {
+ it("is given type", () => {
+ const html = `
+
+ A warning message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ expect(component.type).toEqual("notice")
+ })
+ })
+ })
+
+ describe("dismissable", () => {
+ describe("when dismissable is set", () => {
+ it("is dismissable", () => {
+ const html = `
+
+ A dismissable message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ expect(component.dismissable).toBe(true)
+ })
+
+ it("and type error, it shows close icon", () => {
+ const html = `
+
+ A dismissable message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ expect(
+ component.querySelector("alchemy-icon[name='close']")
+ ).toBeDefined()
+ })
+ })
+
+ describe("when dismissable is not set", () => {
+ it("is not dismissable", () => {
+ const html = `
+
+ A not dismissable message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ expect(component.dismissable).toBe(false)
+ })
+ })
+ })
+
+ describe("iconName", () => {
+ describe("when 'warning', 'warn' or 'alert' message type is given", () => {
+ ;["warning", "warn", "alert"].forEach((type) => {
+ it("is alert", () => {
+ const html = `
+
+ A ${type} message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ expect(component.iconName).toEqual("alert")
+ })
+ })
+ })
+
+ describe("when 'notice' message type is given", () => {
+ const html = `
+
+ A notice message
+
+ `
+
+ it("is check", () => {
+ const component = renderComponent("alchemy-message", html)
+
+ expect(component.iconName).toEqual("check")
+ })
+ })
+
+ describe("when 'info' or 'hint' message type is given", () => {
+ ;["hint", "info"].forEach((type) => {
+ it("is alert", () => {
+ const html = `
+
+ A ${type} message
+
+ `
+ const component = renderComponent("alchemy-message", html)
+ expect(component.iconName).toEqual("information")
+ })
+ })
+ })
+
+ describe("when 'error' message type is given", () => {
+ const html = `
+
+ A error message
+
+ `
+
+ it("is check", () => {
+ const component = renderComponent("alchemy-message", html)
+
+ expect(component.iconName).toEqual("bug")
+ })
+ })
+
+ describe("when unknown message type is given", () => {
+ const html = `
+
+ A foo message
+
+ `
+
+ it("is the given message type as icon name", () => {
+ const component = renderComponent("alchemy-message", html)
+
+ expect(component.iconName).toEqual("foo")
+ })
+ })
+ })
+})
diff --git a/spec/views/alchemy/admin/elements/element_view_spec.rb b/spec/views/alchemy/admin/elements/element_view_spec.rb
index f08b637e8c..67112b97e5 100644
--- a/spec/views/alchemy/admin/elements/element_view_spec.rb
+++ b/spec/views/alchemy/admin/elements/element_view_spec.rb
@@ -23,7 +23,7 @@
let(:element) { create(:alchemy_element, name: "with_message") }
it "renders the message" do
- is_expected.to have_css('.message:contains("One nice message")')
+ is_expected.to have_css('alchemy-message:contains("One nice message")')
end
context "that contains HTML" do
@@ -35,7 +35,7 @@
end
it "renders the HTML message" do
- is_expected.to have_css('.message h1:contains("One nice message")')
+ is_expected.to have_css('alchemy-message[type="info"] h1:contains("One nice message")')
end
end
end
@@ -51,7 +51,7 @@
end
it "renders the warning" do
- is_expected.to have_css('.warning:contains("One nice warning")')
+ is_expected.to have_css('alchemy-message[type="warning"]:contains("One nice warning")')
end
context "that contains HTML" do
@@ -63,7 +63,7 @@
end
it "renders the HTML warning" do
- is_expected.to have_css('.warning h1:contains("One nice warning")')
+ is_expected.to have_css('alchemy-message[type="warning"] h1:contains("One nice warning")')
end
end
end
diff --git a/spec/views/alchemy/ingredients/select_editor_spec.rb b/spec/views/alchemy/ingredients/select_editor_spec.rb
index cbd17173ed..548dcc3f89 100644
--- a/spec/views/alchemy/ingredients/select_editor_spec.rb
+++ b/spec/views/alchemy/ingredients/select_editor_spec.rb
@@ -30,7 +30,7 @@
end
it "renders a warning" do
- is_expected.to have_css(".warning")
+ is_expected.to have_css('alchemy-message[type="warning"]')
end
end