webface_rails 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +37 -0
- data/Rakefile +7 -0
- data/app/controllers/webface_error_report_controller.rb +12 -0
- data/app/helpers/webface_component_helper.rb +248 -0
- data/app/helpers/webface_form.rb +161 -0
- data/app/views/webface_component_templates/_button.html.haml +2 -0
- data/app/views/webface_component_templates/_dialog_window.html.haml +6 -0
- data/app/views/webface_component_templates/_modal_window.html.haml +5 -0
- data/app/views/webface_component_templates/_simple_notification.html.haml +4 -0
- data/app/views/webface_components/_button.html.haml +11 -0
- data/app/views/webface_components/_checkbox.html.haml +21 -0
- data/app/views/webface_components/_editable_select.html.haml +8 -0
- data/app/views/webface_components/_field_hints.html.haml +7 -0
- data/app/views/webface_components/_hidden_form_field.html.haml +1 -0
- data/app/views/webface_components/_hint.html.haml +9 -0
- data/app/views/webface_components/_hint_trigger.html.haml +15 -0
- data/app/views/webface_components/_numeric_form_field.html.haml +13 -0
- data/app/views/webface_components/_password_form_field.html.haml +12 -0
- data/app/views/webface_components/_post_button_link.html.haml +4 -0
- data/app/views/webface_components/_radio.html.haml +18 -0
- data/app/views/webface_components/_select.html.haml +34 -0
- data/app/views/webface_components/_text_form_field.html.haml +12 -0
- data/app/views/webface_components/_text_form_field_with_validation.html.haml +6 -0
- data/app/views/webface_components/_textarea_form_field.html.haml +11 -0
- data/app/views/webface_components/shared/_editable_select_base.html.haml +34 -0
- data/lib/generators/templates/application.js +53 -0
- data/lib/generators/templates/mocha.css +10 -0
- data/lib/generators/templates/mocha.pug +15 -0
- data/lib/generators/templates/my_component.test.js +15 -0
- data/lib/generators/templates/run_webface_test +11 -0
- data/lib/generators/templates/test_animator.js +66 -0
- data/lib/generators/templates/test_utils.js +6 -0
- data/lib/generators/templates/webface.test.js +2 -0
- data/lib/generators/templates/webface_init.js +10 -0
- data/lib/generators/templates/webface_test_server.js +68 -0
- data/lib/generators/webface_generator.rb +81 -0
- data/lib/tasks/webface_rails_tasks.rake +3 -0
- data/lib/webface_rails.rb +11 -0
- data/lib/webface_rails/version.rb +3 -0
- data/spec/helpers/webface_component_helper_spec.rb +133 -0
- data/spec/helpers/webface_form_spec.rb +58 -0
- data/spec/spec_helper.rb +15 -0
- metadata +147 -0
@@ -0,0 +1,6 @@
|
|
1
|
+
.modalWindow{ data: { component_template: "DialogWindowComponent", component_attribute_properties: "close_on_escape,close_on_background_click,show_close_button" }}
|
2
|
+
.modal{ aria: { hidden: "true" } }
|
3
|
+
.modalDialog
|
4
|
+
= image_tag "close_modal_window.svg", class: "close", data: { component_part: "close" }
|
5
|
+
.content{ data: { component_part: "content"} }
|
6
|
+
.buttonContainer{ data: { component_part: "button_container"}}
|
@@ -0,0 +1,5 @@
|
|
1
|
+
.modalWindow{ data: { component_template: "ModalWindowComponent", component_attribute_properties: "close_on_escape,close_on_background_click,show_close_button" }}
|
2
|
+
|
3
|
+
.modal{ aria: { hidden: "true" } }
|
4
|
+
.modalDialog{ data: { component_part: "content"} }
|
5
|
+
= image_tag "close_modal_window.svg", class: "close", data: { component_part: "close" }
|
@@ -0,0 +1,4 @@
|
|
1
|
+
.simpleNotification{ data: { component_template: "SimpleNotificationComponent", component_attribute_properties: "autohide_delay,permanent,container_selector", component_roles: "simple_notification" }}
|
2
|
+
.container
|
3
|
+
.message{ data: { component_property: "message", component_part: "message" }}
|
4
|
+
= image_tag "close_simple_notification.svg", class: "close", data: { component_part: "close" }
|
@@ -0,0 +1,11 @@
|
|
1
|
+
- attrs[:data] ||= {}
|
2
|
+
- attrs[:data][:confirmation] = attrs[:confirmation]
|
3
|
+
- attrs[:data][:disabled] = options[:disabled]
|
4
|
+
- attrs[:data][:prevent_native_click_event] = "false"
|
5
|
+
|
6
|
+
= component_block (attrs[:confirmation] ? "confirmable_button" : "button"), :button, attrs.merge(type: (options[:type] || "submit"), name: options[:name], class: "button #{attrs[:class]}", property_attr: "lockable,disabled,confirmation,prevent_native_click_event") do
|
7
|
+
- if block
|
8
|
+
= capture(&block)
|
9
|
+
- else
|
10
|
+
= attrs[:caption] || attrs[:value]
|
11
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
- val = get_value_for_field_name(options)
|
2
|
+
|
3
|
+
%div.checkboxContainer
|
4
|
+
= hidden_field_tag options[:name], 0
|
5
|
+
= component_block :checkbox, "input",
|
6
|
+
name: options[:name],
|
7
|
+
id: options[:name],
|
8
|
+
type: "checkbox",
|
9
|
+
class: "#{attrs.delete(:class)} checkbox",
|
10
|
+
roles: attrs.delete(:roles),
|
11
|
+
tabindex: options[:tabindex] || 1,
|
12
|
+
property_attr: "name,disabled,checked",
|
13
|
+
data: { disabled: attrs[:disabled], checked: attrs[:checked], component_attribute_properties: "name:name,disabled:disabled,checked:checked" },
|
14
|
+
checked: (val.nil? ? attrs[:checked] : val),
|
15
|
+
disabled: attrs[:disabled]
|
16
|
+
|
17
|
+
%label{ for: options[:name] }
|
18
|
+
= options[:label]
|
19
|
+
- unless options[:popup_hint].blank?
|
20
|
+
= component :hint_trigger, i18n: options[:popup_hint]
|
21
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,8 @@
|
|
1
|
+
= component_block :editable_select, "div",
|
2
|
+
class: (attrs.delete(:class) || "") + " field editableSelectComponent#{" errors" if options[:model] && options[:name] && !get_error_for_field_name(options).blank?}",
|
3
|
+
tabindex: options[:tabindex] || 1,
|
4
|
+
property_attr: 'disabled,name,fetch_url,allow_custom_value,query_param_name',
|
5
|
+
roles: attrs.delete(:roles),
|
6
|
+
data: { disabled: (attrs.delete(:disabled) ? "disabled" : nil), fetch_url: options[:fetch_url], allow_custom_value: options[:allow_custom_value], query_param_name: options[:query_param_name] } do
|
7
|
+
|
8
|
+
= render partial: "components/shared/editable_select_base", locals: { options: options, attrs: attrs }
|
@@ -0,0 +1,7 @@
|
|
1
|
+
.hintsContainer
|
2
|
+
.errorHint
|
3
|
+
= property :validation_errors_summary, "div", class: "validationErrors", style: ("display: block;" unless get_error_for_field_name(options).blank?) do
|
4
|
+
- if options[:model] && options[:attr_name] && errors = get_error_for_field_name(options)
|
5
|
+
= errors.join("; ")
|
6
|
+
.hint
|
7
|
+
= options[:hint]
|
@@ -0,0 +1 @@
|
|
1
|
+
= component_block (attrs[:as_property] ? nil : :form_field), "input", { type: "hidden", name: options[:name], value: get_value_for_field_name(options) }.merge(attrs)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
= component_block :hint, "div",
|
2
|
+
{ class: 'helpHint', property_attr: "anchor,show_events,force_show_events,autoshow_delay,autohide_delay,display_limit,keep_open_when_hover,hint_id", roles: attrs.delete(:roles),
|
3
|
+
data: data_attrs_and_values!(:attrs, binding, [:anchor,:show_events,:force_show_events,:autoshow_delay,:autohide_delay,:display_limit,:keep_open_when_hover,:hint_id]) } do
|
4
|
+
|
5
|
+
.arrowUp
|
6
|
+
.content
|
7
|
+
= image_tag "close_hint.svg", class: "close", data: { component_part: "close" }
|
8
|
+
= capture(&block)
|
9
|
+
.arrowDown
|
@@ -0,0 +1,15 @@
|
|
1
|
+
= image_tag "hint_trigger.svg", class: "hintTrigger", id: (id = "hint_#{attrs.delete(:id)}_#{attrs[:i18n].to_s.sha512_hash(Rails.application.class.parent_name.to_s, 15)}")
|
2
|
+
|
3
|
+
- attrs.merge!({anchor: id, show_events: "mouseover", force_show_events: "click,touchend", autohide_delay: 10, roles: "hint", display_limit: 1, hint_id: id})
|
4
|
+
|
5
|
+
= component :hint, attrs do
|
6
|
+
- if attrs.has_key?(:ma_term)
|
7
|
+
= MultiAsset.t("hints.#{attrs[:ma_term]}")
|
8
|
+
- elsif attrs.has_key?(:title)
|
9
|
+
= attrs[:title]
|
10
|
+
- else
|
11
|
+
= I18n.t("hints.#{attrs[:i18n]}")
|
12
|
+
|
13
|
+
- attrs.delete(:ma_term)
|
14
|
+
- attrs.delete(:title)
|
15
|
+
- attrs.delete(:i18n)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
= component_block :numeric_form_field, "div",
|
2
|
+
{ class: (attrs.delete(:class) || "") + " field numeric#{" errors" if options[:model] && !get_error_for_field_name(options).blank?}", property_attr: "max_length", data: { max_length: attrs.delete(:max_length) }}.merge(attrs) do
|
3
|
+
|
4
|
+
- if options[:label]
|
5
|
+
%label
|
6
|
+
= options[:label]
|
7
|
+
- unless options[:popup_hint].blank?
|
8
|
+
= component :hint_trigger, i18n: options[:popup_hint]
|
9
|
+
|
10
|
+
.fieldAndHint
|
11
|
+
%input{ type: "text", tabindex: options[:tabindex], name: options[:name], value: get_value_for_field_name(options), maxlength: attrs.delete(:maxlength), data: { component_part: 'value_holder', component_attribute_properties: "name:name,disabled:disabled"}}
|
12
|
+
= options[:suffix]
|
13
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,12 @@
|
|
1
|
+
= component_block :form_field, "div",
|
2
|
+
{ class: (attrs.delete(:class) || "") + " field text#{" errors" if options[:model] && !get_error_for_field_name(options).blank?}", property_attr: "disabled,max_length", data: { max_length: attrs.delete(:max_length) }}.merge(attrs) do
|
3
|
+
|
4
|
+
- if options[:label]
|
5
|
+
%label
|
6
|
+
= options[:label]
|
7
|
+
- unless options[:popup_hint].blank?
|
8
|
+
= component :hint_trigger, i18n: options[:popup_hint]
|
9
|
+
|
10
|
+
%input{ type: (attrs.delete(:type) || "password"), tabindex: options[:tabindex], name: options[:name], value: get_value_for_field_name(options), autocomplete: attrs.delete(:autocomplete), data: { component_part: 'value_holder', component_attribute_properties: "name:name" }}
|
11
|
+
= options[:suffix]
|
12
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,18 @@
|
|
1
|
+
= component_block :radio_button, "div",
|
2
|
+
class: (attrs.delete(:class) || "") + " radio #{"errors" if options[:model] && !get_error_for_field_name(options).blank? }",
|
3
|
+
property_attr: "disabled,value",
|
4
|
+
roles: attrs.delete(:roles),
|
5
|
+
data: { max_length: attrs.delete(:max_length), disabled: options[:disabled], value: get_value_for_field_name(options) }.merge(attrs) do
|
6
|
+
|
7
|
+
.container
|
8
|
+
- prepare_select_collection(options[:collection] || get_value_for_field_name(options)).each do |k,v|
|
9
|
+
.item
|
10
|
+
- if options[:selected] == k
|
11
|
+
%input{ type: "radio", name: options[:name], id: "radio_#{options[:radio_options_id]}_#{k}", disabled: options[:disabled], value: k, checked: "checked", data: { component_part: 'option', component_attribute_properties: "name:name" }}
|
12
|
+
- else
|
13
|
+
%input{ type: "radio", name: options[:name], id: "radio_#{options[:radio_options_id]}_#{k}", disabled: options[:disabled], value: k, data: { component_part: 'option', component_attribute_properties: "name:name" }}
|
14
|
+
%label{ for: "radio_#{options[:radio_options_id]}_#{k}" }
|
15
|
+
= v
|
16
|
+
- unless options[:popup_hint].blank?
|
17
|
+
= component :hint_trigger, i18n: options[:popup_hint].delete_at(0)
|
18
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,34 @@
|
|
1
|
+
= component_block (attrs[:roles] == "timezone_selector" ? "timezone_select" : "select"), :div,
|
2
|
+
class: (attrs.delete(:class) || "") + " field selectComponent#{" errors" if options[:model] && !get_error_for_field_name(options).blank?}",
|
3
|
+
tabindex: options[:tabindex] || 1,
|
4
|
+
property_attr: 'disabled,name,fetch_url,separators_below,top_values',
|
5
|
+
roles: attrs.delete(:roles),
|
6
|
+
data: { disabled: attrs.delete(:disabled) ? 'disabled' : nil, fetch_url: options[:fetch_url], name: options[:name], separators_below: options[:separators_below], top_values: options[:top_values] } do
|
7
|
+
|
8
|
+
- if options[:label]
|
9
|
+
%label
|
10
|
+
= options[:label]
|
11
|
+
- unless options[:popup_hint].blank?
|
12
|
+
= component :hint_trigger, i18n: options[:popup_hint]
|
13
|
+
|
14
|
+
= property "input_value", "input",
|
15
|
+
type: 'hidden',
|
16
|
+
data: { component_attribute_properties: 'input_value:value' },
|
17
|
+
name: options[:name], value: get_value_for_field_name(options) || select_component_get_selected_value(options, type=:input)
|
18
|
+
|
19
|
+
.selectBoxAndOptions
|
20
|
+
|
21
|
+
= component_part :selectbox, "div", class: "selectBox" do
|
22
|
+
= property "display_value", "div", class: "value" do
|
23
|
+
= (select_component_get_selected_value(options, :display) if select_component_get_selected_value(options).blank?)
|
24
|
+
= image_tag "arrow_down.svg", class: "arrow"
|
25
|
+
= image_tag "ajax_indicators/rolling_dark.svg", class: 'ajaxIndicator'
|
26
|
+
|
27
|
+
= component_part :options_container, "div", class: "optionsContainer" do
|
28
|
+
- options[:has_blank] = true if options[:has_blank].nil?
|
29
|
+
- prepare_select_collection(options[:collection], selected: options[:selected] || params[options[:name]], blank_option: options[:has_blank]).each do |k,v|
|
30
|
+
= component_part :option, "div", class: "option", data: { option_value: k } do
|
31
|
+
= v
|
32
|
+
|
33
|
+
= component_part :option_template, "div", class: "option"
|
34
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,12 @@
|
|
1
|
+
= component_block :form_field, "div",
|
2
|
+
{ class: (attrs.delete(:class) || "") + " field text#{" errors" if options[:model] && !get_error_for_field_name(options).blank?}", property_attr: "disabled", data: { disabled: options[:disabled] }}.merge(attrs) do
|
3
|
+
|
4
|
+
- if options[:label]
|
5
|
+
%label
|
6
|
+
= options[:label]
|
7
|
+
- unless options[:popup_hint].blank?
|
8
|
+
= component :hint_trigger, i18n: options[:popup_hint]
|
9
|
+
|
10
|
+
%input{ type: (attrs.delete(:type) || "text"), tabindex: options[:tabindex], name: options[:name], value: attrs[:value] || get_value_for_field_name(options), autofocus: attrs[:autofocus], disabled: options[:disabled], maxlength: attrs.delete(:maxlength), autocomplete: attrs.delete(:autocomplete), data: { component_part: 'value_holder', component_attribute_properties: "name:name" }}
|
11
|
+
= options[:suffix]
|
12
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,6 @@
|
|
1
|
+
= component_block :form_field_with_validation, "div",
|
2
|
+
{ class: (attrs.delete(:class) || "") + " field text#{" errors" if options[:model] && !get_error_for_field_name(options).blank?}", property_attr: "disabled,label", data: { disabled: options[:disabled], label: options[:label] } }.merge(attrs) do
|
3
|
+
|
4
|
+
%input{ type: (attrs.delete(:type) || "text"), tabindex: options[:tabindex], name: options[:name], value: attrs[:value] || get_value_for_field_name(options), autofocus: options[:autofocus], disabled: options[:disabled], maxlength: attrs.delete(:maxlength), autocomplete: attrs.delete(:autocomplete), data: { component_part: 'value_holder', component_attribute_properties: "name:name" } }
|
5
|
+
= options[:suffix]
|
6
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,11 @@
|
|
1
|
+
= component_block :form_field, "div",
|
2
|
+
{ class: (attrs.delete(:class) || "") + " field textarea#{" errors" if options[:model] && !get_error_for_field_name(options).blank?}", property_attr: "disabled"}.merge(attrs) do
|
3
|
+
|
4
|
+
- if options[:label]
|
5
|
+
%label
|
6
|
+
= options[:label]
|
7
|
+
- unless options[:popup_hint].blank?
|
8
|
+
= component :hint_trigger, i18n: options[:popup_hint]
|
9
|
+
|
10
|
+
%textarea{ name: options[:name], maxlength: attrs.delete(:maxlength), tabindex: options[:tabindex], data: { component_part: 'value_holder', component_attribute_properties: "name:name" }}= get_value_for_field_name(options)
|
11
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,34 @@
|
|
1
|
+
- if options[:label]
|
2
|
+
%label
|
3
|
+
= options[:label]
|
4
|
+
- unless options[:popup_hint].blank?
|
5
|
+
= component :hint_trigger, i18n: options[:popup_hint]
|
6
|
+
|
7
|
+
.selectBoxAndOptions
|
8
|
+
|
9
|
+
= component_part :selectbox, "div", class: "selectBox" do
|
10
|
+
= property "display_value", "input",
|
11
|
+
type: 'text',
|
12
|
+
class: 'editableSelect',
|
13
|
+
placeholder: t("views.start_typing"),
|
14
|
+
autocomplete: "off",
|
15
|
+
maxlength: attrs.delete(:maxlength),
|
16
|
+
value: (select_component_get_selected_value(options, :display) if select_component_get_selected_value(options).blank?),
|
17
|
+
data: { component_attribute_properties: 'display_value:value', component_part: "display_input" }
|
18
|
+
= image_tag "ajax_indicators/rolling_dark.svg", class: 'ajaxIndicator'
|
19
|
+
= component_part :arrow, "span" do
|
20
|
+
= image_tag "arrow_down.svg", class: "arrow"
|
21
|
+
= property "input_value", "input", type: 'hidden', name: options[:name], value: get_value_for_field_name(options), data: { component_part: "input", component_attribute_properties: 'input_value:value' }
|
22
|
+
|
23
|
+
= component_part :options_container, "div", class: "optionsContainer" do
|
24
|
+
- prepare_select_collection(options[:collection] || [], selected: options[:selected] || params[options[:name]], blank_option: false).each do |k,v|
|
25
|
+
= component_part :option, "div", class: "option", data: { option_value: k } do
|
26
|
+
= v
|
27
|
+
|
28
|
+
.noOptionsFoundMessage
|
29
|
+
%p.main= t("views.components.editable_select.no_matching_items")
|
30
|
+
- if options[:allow_custom_value]
|
31
|
+
%p.customValue= t("views.components.editable_select.you_can_leave_typed")
|
32
|
+
|
33
|
+
= component_part :option_template, "div", class: "option"
|
34
|
+
= render partial: "components/field_hints", locals: { options: options }
|
@@ -0,0 +1,53 @@
|
|
1
|
+
// Loading Webface components one by one. We could load webface.js instead (in that case, we wouldn't need to define Webface variable below
|
2
|
+
// but we wanted to give more flexibility here. Remove components you don't actually intend to use. Less to load!
|
3
|
+
import { Logmaster } from './path_to_webface/logmaster.js'
|
4
|
+
import { Animator } from './path_to_webface/animator.js'
|
5
|
+
import { AjaxRequest } from './path_to_webface/ajax_request.js'
|
6
|
+
import { Component } from './path_to_webface/component.js'
|
7
|
+
import { RootComponent } from './path_to_webface/components/root_component.js'
|
8
|
+
import { ButtonComponent } from './path_to_webface/components/button_component.js'
|
9
|
+
import { CheckboxComponent } from './path_to_webface/components/checkbox_component.js'
|
10
|
+
import { FormFieldComponent } from './path_to_webface/components/form_field_component.js'
|
11
|
+
import { NumericFormFieldComponent } from './path_to_webface/components/numeric_form_field_component.js'
|
12
|
+
import { ModalWindowComponent } from './path_to_webface/components/modal_window_component.js'
|
13
|
+
import { DialogWindowComponent } from './path_to_webface/components/dialog_window_component.js'
|
14
|
+
import { RadioButtonComponent } from './path_to_webface/components/radio_button_component.js'
|
15
|
+
import { HintComponent } from './path_to_webface/components/hint_component.js'
|
16
|
+
import { SimpleNotificationComponent } from './path_to_webface/components/simple_notification_component.js'
|
17
|
+
import { SelectComponent } from './path_to_webface/components/select_component.js'
|
18
|
+
import { ConfirmableButtonComponent } from './path_to_webface/components/confirmable_button_component.js'
|
19
|
+
import { ContextMenuComponent } from './path_to_webface/components/context_menu_component.js'
|
20
|
+
|
21
|
+
// Import your own components like this:
|
22
|
+
// import { MyComponent } from "./components/my_component.js";
|
23
|
+
|
24
|
+
window.webface.substitute_classes = { "Animator": Animator }
|
25
|
+
|
26
|
+
// Change these settings to your liking
|
27
|
+
AjaxRequest.display_40x = true;
|
28
|
+
AjaxRequest.log_request = true;
|
29
|
+
AjaxRequest.log_response = true;
|
30
|
+
AjaxRequest.user_notification = (message) => {
|
31
|
+
// Put code that informs user of a bad ajax-request here
|
32
|
+
// By default if does nothing.
|
33
|
+
};
|
34
|
+
|
35
|
+
export var Webface = {
|
36
|
+
|
37
|
+
"init": (root_el=document.querySelector("body")) => {
|
38
|
+
|
39
|
+
window.webface["logger"] = new Logmaster({test_env: false, reporters: { "console" : "DEBUG", "http" : "ERROR" }, throw_errors: true, http_report_url: "/report_webface_error"})
|
40
|
+
|
41
|
+
try {
|
42
|
+
var root = new RootComponent();
|
43
|
+
root.dom_element = root_el;
|
44
|
+
root.initChildComponents();
|
45
|
+
} catch(e) {
|
46
|
+
window.webface.logger.capture(e, { log_level: "ERROR" });
|
47
|
+
}
|
48
|
+
|
49
|
+
},
|
50
|
+
|
51
|
+
}
|
52
|
+
|
53
|
+
Webface.init();
|
@@ -0,0 +1,10 @@
|
|
1
|
+
body {
|
2
|
+
font-family: "Ubuntu Mono", Arial; font-size: 70%;
|
3
|
+
}
|
4
|
+
|
5
|
+
li h2 { font-weight: normal; cursor: pointer; }
|
6
|
+
li span.duration { padding-left: 2em; font-weight: normal; }
|
7
|
+
li pre { font-size: 150%; margin-left: 2em; background-color: #ddd; border-left: solid 4px #aaa; padding: 1em; }
|
8
|
+
|
9
|
+
li.fail h2 { color: #D11111; padding: 0.2em; font-weight: bold; }
|
10
|
+
li.fail pre { background-color: #FFCFCF; color: #AD0000; border-left: solid 4px #FF6161; }
|
@@ -0,0 +1,15 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
meta(charset="utf-8")
|
5
|
+
link(rel="stylesheet" href="mocha.css")
|
6
|
+
body
|
7
|
+
#mocha
|
8
|
+
script(src="node_modules/chai/chai.js")
|
9
|
+
script(src="node_modules/chai-spies/chai-spies.js")
|
10
|
+
script(src="node_modules/mocha/mocha.js")
|
11
|
+
script.
|
12
|
+
mocha.setup('bdd')
|
13
|
+
script(src=file, type="module")
|
14
|
+
script(type="module").
|
15
|
+
mocha.checkLeaks(); mocha.run();
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import '../webface_init.js'
|
2
|
+
|
3
|
+
// Uncomment and replace with the component you're testing
|
4
|
+
//
|
5
|
+
import { MyComponent } from '../source/components/my_component.js'
|
6
|
+
|
7
|
+
describe("MyComponent", async function() {
|
8
|
+
|
9
|
+
it("test", function() {
|
10
|
+
// Uncomment to see if a new component instance can be created and component class has been loaded sucessfully.
|
11
|
+
//
|
12
|
+
new MyComponent();
|
13
|
+
});
|
14
|
+
|
15
|
+
});
|
@@ -0,0 +1,66 @@
|
|
1
|
+
// This class exists to not apply animations when in unit tests, since we can't really
|
2
|
+
// wait for animation Promises to resovle. Instead, we use this custom TestAnimator
|
3
|
+
// to show/hide elements and resolve Promises immediately.
|
4
|
+
export var TestAnimator = {
|
5
|
+
|
6
|
+
// IMPORTANT: we have to use a manual Promise creation inside animation methods here
|
7
|
+
// because browsers don't yet support Animation.finished. See https://developer.mozilla.org/en-US/docs/Web/API/Animation/finished
|
8
|
+
|
9
|
+
"show": function(elements, ms, { display_value="block" }={}) {
|
10
|
+
return this._applyToCollection(elements, (el) => {
|
11
|
+
el.style.display = "block";
|
12
|
+
el.style.opacity = "1";
|
13
|
+
return new Promise((resolve, reject) => {
|
14
|
+
resolve("show behavior resolved");
|
15
|
+
});
|
16
|
+
});
|
17
|
+
},
|
18
|
+
|
19
|
+
"hide": function(elements, ms) {
|
20
|
+
return this._applyToCollection(elements, (el) => {
|
21
|
+
el.style.display = "none";
|
22
|
+
el.style.opacity = "0";
|
23
|
+
return new Promise((resolve, reject) => {
|
24
|
+
resolve("hide behavior resolved");
|
25
|
+
});
|
26
|
+
});
|
27
|
+
},
|
28
|
+
|
29
|
+
"scrollDown": function(elements, ms, { display_value="block" }={}) {
|
30
|
+
return this._applyToCollection(elements, (el) => {
|
31
|
+
el.style.display = "block";
|
32
|
+
el.style.opacity = "1";
|
33
|
+
return new Promise((resolve, reject) => {
|
34
|
+
resolve("scrollDown behavior resolved");
|
35
|
+
});
|
36
|
+
});
|
37
|
+
},
|
38
|
+
|
39
|
+
"scrollUp": function(elements, ms) {
|
40
|
+
return this._applyToCollection(elements, (el) => {
|
41
|
+
el.style.display = "none";
|
42
|
+
el.style.opacity = "0";
|
43
|
+
return new Promise((resolve, reject) => {
|
44
|
+
resolve("scrollUp behavior resolved");
|
45
|
+
});
|
46
|
+
});
|
47
|
+
},
|
48
|
+
|
49
|
+
"_applyToCollection": function(elements, func) {
|
50
|
+
if(!(elements instanceof Array))
|
51
|
+
elements = [elements];
|
52
|
+
|
53
|
+
elements = elements.map((el) => {
|
54
|
+
if(/Component$/.test(el.constructor.name))
|
55
|
+
return el.dom_element;
|
56
|
+
else
|
57
|
+
return el;
|
58
|
+
});
|
59
|
+
|
60
|
+
return Promise.all(elements.map((el) => {
|
61
|
+
return func(el);
|
62
|
+
}));
|
63
|
+
|
64
|
+
}
|
65
|
+
|
66
|
+
}
|