brut 0.0.20 → 0.0.21
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/brut/back_end/seed_data.rb +19 -2
- data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
- data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
- data/lib/brut/back_end/sidekiq.rb +2 -1
- data/lib/brut/back_end/validator.rb +5 -1
- data/lib/brut/back_end.rb +4 -2
- data/lib/brut/cli.rb +4 -3
- data/lib/brut/factory_bot.rb +0 -5
- data/lib/brut/framework/app.rb +70 -5
- data/lib/brut/framework/config.rb +5 -3
- data/lib/brut/framework/container.rb +3 -2
- data/lib/brut/framework/errors.rb +12 -4
- data/lib/brut/framework/mcp.rb +62 -1
- data/lib/brut/framework/project_environment.rb +6 -2
- data/lib/brut/framework.rb +1 -1
- data/lib/brut/front_end/component.rb +35 -12
- data/lib/brut/front_end/components/constraint_violations.rb +1 -1
- data/lib/brut/front_end/components/form_tag.rb +1 -1
- data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
- data/lib/brut/front_end/components/inputs/text_field.rb +1 -1
- data/lib/brut/front_end/components/time_tag.rb +1 -1
- data/lib/brut/front_end/layout.rb +16 -0
- data/lib/brut/front_end/page.rb +51 -26
- data/lib/brut/front_end/routing.rb +5 -1
- data/lib/brut/front_end.rb +4 -13
- data/lib/brut/i18n/base_methods.rb +37 -3
- data/lib/brut/i18n/for_back_end.rb +3 -0
- data/lib/brut/i18n/for_cli.rb +3 -0
- data/lib/brut/i18n/http_accept_language.rb +47 -0
- data/lib/brut/instrumentation/open_telemetry.rb +25 -0
- data/lib/brut/instrumentation.rb +3 -5
- data/lib/brut/sinatra_helpers.rb +1 -0
- data/lib/brut/spec_support/component_support.rb +18 -4
- data/lib/brut/spec_support/e2e_support.rb +1 -1
- data/lib/brut/spec_support/general_support.rb +3 -0
- data/lib/brut/spec_support/handler_support.rb +6 -1
- data/lib/brut/spec_support/matcher.rb +1 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
- data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
- data/lib/brut/spec_support.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +5 -4
- metadata +2 -9
- data/doc-src/architecture.md +0 -102
- data/doc-src/assets.md +0 -98
- data/doc-src/forms.md +0 -214
- data/doc-src/handlers.md +0 -83
- data/doc-src/javascript.md +0 -265
- data/doc-src/keyword-injection.md +0 -183
- data/doc-src/pages.md +0 -210
- data/doc-src/route-hooks.md +0 -59
data/lib/brut/front_end/page.rb
CHANGED
@@ -1,46 +1,44 @@
|
|
1
|
-
# A
|
2
|
-
#
|
1
|
+
# A Page backs a web page, which handles rendering everything in a browser window when a URL is requested.
|
2
|
+
# Technically, a page is identical to a {Brut::FrontEnd::Component}, except that a page has a layout.
|
3
|
+
# A {Brut::FrontEnd::Layout} is common HTML that surrounds your page's HTML.
|
4
|
+
# Your page is a Phlex component, but instead of implementing `view_template`, you
|
5
|
+
# implement {#page_template} to ensure the layout is used.
|
3
6
|
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
+
# To create a page, after defining a route, subclass this class (or, more likely, your app's `AppPage`) and
|
8
|
+
# provide an initializer that accepts keyword arguments. The names of these arguments will be used to locate the
|
9
|
+
# values that Brut will pass in when creating your page object.
|
7
10
|
#
|
8
|
-
#
|
9
|
-
# app. For example, if you have a page named `Auth::LoginPage`, it would expected to be in
|
10
|
-
# `app/src/front_end/pages/auth/login_page.rb`. Thus, Brut will also expect
|
11
|
-
# `app/src/front_end/pages/auth/login_page.html.erb` to exist as well. That ERB file is used with an instance of your
|
12
|
-
# pages's class to render the page's HTML.
|
11
|
+
# Consult Brut's documentation on keyword injection to know what values you may use and how values are located.
|
13
12
|
#
|
14
13
|
# @see Brut::FrontEnd::Component
|
14
|
+
# @see Brut::FrontEnd::Layout
|
15
15
|
class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
16
16
|
include Brut::FrontEnd::HandlingResults
|
17
17
|
|
18
|
-
# Returns the name of the layout for this page. This string is used to find
|
19
|
-
#
|
18
|
+
# Returns the name of the layout for this page. This string is used to find a class named
|
19
|
+
# `«camelized-layout»Layout` in your app. The default value is "default", meaning that the class
|
20
|
+
# `DefaultLayout` will be used.
|
20
21
|
#
|
21
|
-
# Note that the layout can be dynamic. It is requested when {#
|
22
|
+
# Note that the layout can be dynamic. It is requested when {#page_template} is called, so you can override this
|
22
23
|
# method and use any ivar set in your constructor to change what layout is used.
|
23
24
|
#
|
25
|
+
# If your page does not need a layout, you have two options:
|
26
|
+
#
|
27
|
+
# * Create your own blank layout named, e.g. `BlankLayout` and have this method return `"blank"`.
|
28
|
+
# * Implement `view_template` instead of `page_template`, thus overriding this class' implementation that uses
|
29
|
+
# layouts.
|
30
|
+
#
|
24
31
|
# @return [String] The name of the layout. May not be `nil`.
|
25
32
|
def layout = "default"
|
26
33
|
|
27
|
-
# Called after the page is created, but before {#
|
34
|
+
# Called after the page is created, but before {#page_template} is called. This allows you to do any pre-flight checks and potentially
|
28
35
|
# redirect the user or produce an error.
|
29
36
|
#
|
30
|
-
# @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and
|
37
|
+
# @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and no HTML is generated. If you return a {Brut::FrontEnd::HttpStatus} (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#http_status}), HTML generation is skipped and that status is returned with no content. If anything else is returned, HTML is generated normal.
|
31
38
|
def before_render = nil
|
32
39
|
|
33
|
-
|
34
|
-
|
35
|
-
layout_class = RichString.new([
|
36
|
-
self.layout,
|
37
|
-
"layout"
|
38
|
-
].join("_")).camelize
|
39
|
-
)
|
40
|
-
render layout_class.new(page_name:,&block)
|
41
|
-
end
|
42
|
-
|
43
|
-
|
40
|
+
# Core method of this class. Do not override. This handles the use of {#before_render} and is what Brut
|
41
|
+
# calls to possibly render the page.
|
44
42
|
def handle!
|
45
43
|
case before_render
|
46
44
|
in URI => uri
|
@@ -52,6 +50,15 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
53
|
+
# Override this method to produce your page's HTML. You are intended to call Phlex
|
54
|
+
# methods here. Anything you can do inside the Phlex-standard `view_template` method, you can
|
55
|
+
# do here. The only difference is that this will all be rendered in the context of your configured
|
56
|
+
# {#layout}.
|
57
|
+
def page_template = abstract_method!
|
58
|
+
|
59
|
+
# Phlex's API to produce markup. Do not override this or you will lose your layout.
|
60
|
+
# This implementation locates the configured layout, renders it, and renders {#page_template}
|
61
|
+
# inside.
|
55
62
|
def view_template
|
56
63
|
with_layout do
|
57
64
|
page_template
|
@@ -68,6 +75,24 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
|
68
75
|
# @!visibility private
|
69
76
|
def component_name = raise Brut::Framework::Errors::Bug,"#{self.class} is not a component"
|
70
77
|
|
78
|
+
private
|
79
|
+
|
80
|
+
# Locates the layout class and uses it to render itself, along
|
81
|
+
# with the block given.
|
82
|
+
#
|
83
|
+
# @!visibility private
|
84
|
+
def with_layout(&block)
|
85
|
+
layout_class = Module.const_get(
|
86
|
+
layout_class = RichString.new([
|
87
|
+
self.layout,
|
88
|
+
"layout"
|
89
|
+
].join("_")).camelize
|
90
|
+
)
|
91
|
+
render layout_class.new(page_name:,&block)
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
71
96
|
end
|
72
97
|
|
73
98
|
# Holds pages included with the Brut framework
|
@@ -218,7 +218,11 @@ private
|
|
218
218
|
joined_path = joined_path + "#" + URI.encode_uri_component(anchor)
|
219
219
|
end
|
220
220
|
uri = URI(joined_path)
|
221
|
-
|
221
|
+
query_string = URI.encode_www_form(query_string_params)
|
222
|
+
if query_string.to_s.strip != ""
|
223
|
+
uri.query = query_string
|
224
|
+
end
|
225
|
+
|
222
226
|
uri.extend(Phlex::SGML::SafeObject)
|
223
227
|
end
|
224
228
|
|
data/lib/brut/front_end.rb
CHANGED
@@ -1,16 +1,7 @@
|
|
1
|
-
# In Brut, the _front end_ is considered anything that interacts directly
|
2
|
-
#
|
3
|
-
# and
|
4
|
-
#
|
5
|
-
# You {Brut::App} defines pages, forms, and actions. A page is backed by a subclass of {Brut::FrontEnd::Page}, which provides
|
6
|
-
# dynamic data for rendering. A page can reference {Brut::FrontEnd::Component} subclasses to allow functional decomposition of front
|
7
|
-
# end logic and markup, as well as re-use. Both pages and components have ERB files that describe the HTML to be rendered.
|
8
|
-
#
|
9
|
-
# A {Brut::FrontEnd::Form} subclass defines a form that a browser will submit to your app. That
|
10
|
-
# submission is processed by a {Brut::FrontEnd::Handler} subclass. Handlers can also respond to other HTTP requests.
|
11
|
-
#
|
12
|
-
# In addition to responding to requests, you can subclass {Brut::FrontEnd::RouteHook} or {Brut::FrontEnd::Middleware} to perform
|
13
|
-
# further manipulation of the request.
|
1
|
+
# In Brut, the _front end_ is considered anything that interacts directly
|
2
|
+
# with a web browser or HTTP. This includes rendering HTML, managing
|
3
|
+
# JavaScript and CSS, and processing form submissions. It contrasts to
|
4
|
+
# {Brut::BackEnd}, which handles the business logic and database.
|
14
5
|
#
|
15
6
|
# The entire front-end is based on Rack, so you should be able to achieve anything you need to.
|
16
7
|
module Brut::FrontEnd
|
@@ -23,12 +23,12 @@ module Brut::I18n::BaseMethods
|
|
23
23
|
# to `t("email.required", field: "E-mail address")` would generate `"E-mail address is required"`.
|
24
24
|
#
|
25
25
|
# @param [String,Symbol,Array<String>,Array<Symbol>] key used to create one or more keys to be translated.
|
26
|
-
# This value's behavior is designed to
|
26
|
+
# This value's behavior is designed to balance predictabilitiy in what actual key is chosen
|
27
27
|
# but without needless repetition on a page. If this value is provided, and is an array, the values
|
28
28
|
# are joined with "." to form a key. If the value is not an array, that value is used directly.
|
29
29
|
# Given this key, two values are checked for a translation: the key itself and
|
30
30
|
# the key inside "general.". If this value is *not* provided, it is expected
|
31
|
-
#
|
31
|
+
# that the `**rest` hash includes page: or component:. See that parameter and the example.
|
32
32
|
#
|
33
33
|
# @param [Hash] rest values to use for interpolation of the key's translation. If `key` is omitted,
|
34
34
|
# this hash should have a value for either `page:` or `component:` (not both). If
|
@@ -39,6 +39,16 @@ module Brut::I18n::BaseMethods
|
|
39
39
|
# Note that if the page– or component–specific key is not found, this will check
|
40
40
|
# `general.«page: value»`.
|
41
41
|
# @option interpolated_values [Numeric] count Special interpolation to control pluralization.
|
42
|
+
# @yield Nothing is yielded if a block is given, however the value returned is used for the `%{block}`
|
43
|
+
# interpolation value.
|
44
|
+
# @yieldreturn [String] The value to use for the `%{block}` interpolation value. There is some nuance to
|
45
|
+
# how this works. The value returned is given to `capture`, and that value
|
46
|
+
# is given to `safe`. Outside of an HTML-rendering context, these methods
|
47
|
+
# simply pass through the contents of the block. In an HTML-rendering
|
48
|
+
# context, however, these methods are assumed to be from
|
49
|
+
# [`Phlex::HTML`](https://phlex.fun). `capture` will create a new Phlex
|
50
|
+
# context and capture any HTML built inside the block. That HTML is assumed
|
51
|
+
# to be safe, thus `safe` is called to communicate this to Phlex.
|
42
52
|
#
|
43
53
|
# @raise [I18n::MissingTranslation] if no translation is found
|
44
54
|
# @raise [I18n::MissingInterpolationArgument] if interpolation arguments are missing, or if the key
|
@@ -89,7 +99,7 @@ module Brut::I18n::BaseMethods
|
|
89
99
|
# t(page: :new_widget) # => Create new Widget
|
90
100
|
# # in your code for WidgetsPage
|
91
101
|
# t(page: :new_widget) # => Create New
|
92
|
-
# # in your code for
|
102
|
+
# # in your code for SomeOtherPage
|
93
103
|
# t(page: :new_widget) # => Make a New Widget
|
94
104
|
#
|
95
105
|
# @example Using page: with an array
|
@@ -106,6 +116,30 @@ module Brut::I18n::BaseMethods
|
|
106
116
|
# }
|
107
117
|
# # in your code for HomePage
|
108
118
|
# t(page: [ :captions, :new ]) # => New Widgets
|
119
|
+
#
|
120
|
+
# @example Using a block with Phlex
|
121
|
+
# # in your translations file
|
122
|
+
# en: {
|
123
|
+
# greeting: "Hello there %{name}, you may %{block}",
|
124
|
+
# }
|
125
|
+
# # Inside a component where
|
126
|
+
# # Brut::I18n::ForHTML has been included
|
127
|
+
# def view_template
|
128
|
+
# h1 do
|
129
|
+
# raw(t(:greeting), name: user.name) do
|
130
|
+
# a(href: "https://support.example.com") do
|
131
|
+
# "contact support"
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
# end
|
136
|
+
# # This will produce this HTML, assuming user.name is "Pat":
|
137
|
+
# <h1>
|
138
|
+
# Hell there Pat, you may
|
139
|
+
# <a href="https://support.example.com">
|
140
|
+
# contact support
|
141
|
+
# </a>
|
142
|
+
# </h1>
|
109
143
|
def t(key=:look_in_rest,**rest,&block)
|
110
144
|
if key == :look_in_rest
|
111
145
|
|
data/lib/brut/i18n/for_cli.rb
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
+
# Manages the value for the HTTP
|
2
|
+
# [Accept-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language)
|
3
|
+
# header. Generally, you would not interact with this class directly, however it is used
|
4
|
+
# by Brut to make a guess as to which Locale a browser is reporting.
|
1
5
|
class Brut::I18n::HTTPAcceptLanguage
|
6
|
+
# A locale with the weight (value for q=) it was given in the Accept-Language header
|
2
7
|
WeightedLocale = Data.define(:locale, :q) do
|
8
|
+
# Returns the primary locale for whatever locale
|
9
|
+
# this is holding. For example, the primary locale
|
10
|
+
# of "en-US" is "en".
|
3
11
|
def primary_locale = self.locale.gsub(/\-.*$/,"")
|
12
|
+
|
13
|
+
# True if this locale is a primary locale
|
4
14
|
def primary? = self.primary_locale == self.locale
|
5
15
|
|
16
|
+
# Return a new WeightedLocale that is the primary locale.
|
6
17
|
def primary_only
|
7
18
|
self.class.new(locale: self.primary_locale, q: self.q)
|
8
19
|
end
|
@@ -12,6 +23,11 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
12
23
|
end
|
13
24
|
end
|
14
25
|
|
26
|
+
# Parse the value stored in the session.
|
27
|
+
#
|
28
|
+
# @param [String] session_value the value stored in the session.
|
29
|
+
# @return [Brut::I18n::HTTPAcceptLanguage] a usable object. If the provided value
|
30
|
+
# is blank, #{Brut::I18n::HTTPAcceptLanguage::AlwaysEnglish} is returned.
|
15
31
|
def self.from_session(session_value)
|
16
32
|
values = session_value.to_s.split(/,/).map { |value|
|
17
33
|
locale,q = value.split(/;/)
|
@@ -24,6 +40,19 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
24
40
|
end
|
25
41
|
end
|
26
42
|
|
43
|
+
# Parse the value provided by the browser via
|
44
|
+
# {Brut::FrontEnd::Handlers::LocaleDetectionHandler} via
|
45
|
+
# the `brut-locale-detection` custom element (which
|
46
|
+
# uses `Intl.DateTimeFormat().resolvedOptions()` to determine
|
47
|
+
# the locale).
|
48
|
+
#
|
49
|
+
# Because this value is not in the same format as the Accept-Language
|
50
|
+
# header, it's `q` is assumed to be 1.
|
51
|
+
#
|
52
|
+
# @param [String] value the value provided by the brower.
|
53
|
+
#
|
54
|
+
# @return [Brut::I18n::HTTPAcceptLanguage] a usable object. If the provided value
|
55
|
+
# is blank, #{Brut::I18n::HTTPAcceptLanguage::AlwaysEnglish} is returned.
|
27
56
|
def self.from_browser(value)
|
28
57
|
value = value.to_s.strip
|
29
58
|
if value == ""
|
@@ -33,6 +62,10 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
33
62
|
end
|
34
63
|
end
|
35
64
|
|
65
|
+
# Parse from the HTTP Accept-Language header.
|
66
|
+
#
|
67
|
+
# @return [Brut::I18n::HTTPAcceptLanguage] a usable object. If the provided value
|
68
|
+
# is blank, #{Brut::I18n::HTTPAcceptLanguage::AlwaysEnglish} is returned.
|
36
69
|
def self.from_header(header_value)
|
37
70
|
header_value = header_value.to_s.strip
|
38
71
|
if header_value == "*" || header_value == ""
|
@@ -50,14 +83,28 @@ class Brut::I18n::HTTPAcceptLanguage
|
|
50
83
|
end
|
51
84
|
end
|
52
85
|
|
86
|
+
# Ordered list of locales, from highest-weighted to lowest.
|
53
87
|
attr_reader :weighted_locales
|
88
|
+
# @param [Array<Brut::I18n::HTTPAcceptLanguage::WeightedLocale>] weighted_locales locales to use. They do not
|
89
|
+
# need to be ordered
|
54
90
|
def initialize(weighted_locales)
|
55
91
|
@weighted_locales = weighted_locales.sort_by(&:q).reverse
|
56
92
|
end
|
93
|
+
|
94
|
+
# True if the values inside this object represent known locales, and not a guess based on missing information.
|
95
|
+
# In general, this returns true if the values came from the Accept-Language header, or from the browser.
|
57
96
|
def known? = true
|
97
|
+
|
98
|
+
# Serialize for storage in the session
|
99
|
+
#
|
100
|
+
# @return [String] a string that can be stored in the session and later deserialized via {.from_session}.
|
58
101
|
def for_session = @weighted_locales.map { |weighted_locale| "#{weighted_locale.locale};#{weighted_locale.q}" }.join(",")
|
59
102
|
def to_s = self.for_session
|
60
103
|
|
104
|
+
# A subclass that represents the use of English and only English. This is
|
105
|
+
# used when attempts to determine the locale fail. Instances of this class
|
106
|
+
# are considered "unknown" ({#known?} returns false), which allows Brut
|
107
|
+
# to replace this with a known value later on.
|
61
108
|
class AlwaysEnglish < Brut::I18n::HTTPAcceptLanguage
|
62
109
|
def initialize
|
63
110
|
super([ WeightedLocale.new(locale: "en", q: 1) ])
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# Class to interact with the OpenTelemetry standard in a simpler way than
|
2
|
+
# the provided Ruby gem does. In general, you should use this class
|
3
|
+
# via `Brut.container.instrumentation`, and you should *not* use the
|
4
|
+
# OpenTelemetry ruby library directly. You probably wouldn't want to, anyway.
|
1
5
|
class Brut::Instrumentation::OpenTelemetry
|
2
6
|
# Create a span around the given block of code.
|
3
7
|
#
|
@@ -39,11 +43,32 @@ class Brut::Instrumentation::OpenTelemetry
|
|
39
43
|
timestamp:)
|
40
44
|
end
|
41
45
|
|
46
|
+
# Record an exception. In general, use this only if:
|
47
|
+
#
|
48
|
+
# * You need to have the parent span record this particular exception
|
49
|
+
# * You are not going to re-raise the exception.
|
50
|
+
#
|
51
|
+
# Otherwise, look at {#record_and_reraise_exception!}.
|
52
|
+
#
|
53
|
+
# @param [Exception] ex the exception to record.
|
54
|
+
# @param [Hash] attributes any attributes to attach that will show up in your OTel provider
|
42
55
|
def record_exception(ex,attributes=nil)
|
43
56
|
current_span = OpenTelemetry::Trace.current_span
|
44
57
|
current_span.record_exception(ex,attributes: NormalizedAttributes.new(nil,attributes).to_h)
|
45
58
|
end
|
46
59
|
|
60
|
+
# Record an exception and re-raise it. This is useful if you want
|
61
|
+
# the exception recorded as part of the parent span, but still plan
|
62
|
+
# to let it raise. Don't do this for every exception you intend to raise.
|
63
|
+
# @param [Exception] ex the exception to record.
|
64
|
+
# @param [Hash] attributes any attributes to attach that will show up in your OTel provider
|
65
|
+
# @raise [Exception] the exception passed in.
|
66
|
+
def record_and_reraise_exception(ex,attributes=nil)
|
67
|
+
reecord_exception(ex,attributes)
|
68
|
+
raise ex
|
69
|
+
end
|
70
|
+
|
71
|
+
|
47
72
|
# Adds attributes to the span, converting the hash or keyword arguments to strings. This will use
|
48
73
|
# the app's Otel prefix for all attributes, so you do not have to prefix them.
|
49
74
|
# If you need to set standard attributes, you should use {Brut::Instrumentation::OpenTelemetry::Span#add_prefixed_attributes} instead.
|
data/lib/brut/instrumentation.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
+
# Namespace for instrumentation setup and support. Brut strives to provide useful
|
2
|
+
# instrumentation by default.
|
3
|
+
#
|
1
4
|
module Brut::Instrumentation
|
2
5
|
autoload(:OpenTelemetry,"brut/instrumentation/open_telemetry")
|
3
6
|
autoload(:LoggerSpanExporter,"brut/instrumentation/logger_span_exporter")
|
4
|
-
|
5
|
-
# Convenience method to add attributes to create a span without accessing the instrumentation instance directly.
|
6
|
-
def span(name,**attributes,&block)
|
7
|
-
Brut.container.instrumentation.span(name,**attributes,&block)
|
8
|
-
end
|
9
7
|
end
|
10
8
|
|
data/lib/brut/sinatra_helpers.rb
CHANGED
@@ -9,6 +9,7 @@ module Brut::SinatraHelpers
|
|
9
9
|
sinatra_app.path("/__brut/locale_detection",method: :post)
|
10
10
|
sinatra_app.path("/__brut/instrumentation",method: :get)
|
11
11
|
sinatra_app.set :host_authorization, permitted_hosts: Brut.container.permitted_hosts
|
12
|
+
sinatra_app.set :show_exceptions, false
|
12
13
|
end
|
13
14
|
|
14
15
|
# @private
|
@@ -10,9 +10,17 @@ module Brut::SpecSupport::ComponentSupport
|
|
10
10
|
include Brut::SpecSupport::ClockSupport
|
11
11
|
include Brut::I18n::BaseMethods
|
12
12
|
|
13
|
-
# Render a component into its text representation. This mimics what happens when
|
14
|
-
#
|
15
|
-
#
|
13
|
+
# Render a component or page into its text representation. This mimics what happens when Brut renders
|
14
|
+
# the page or component. Note that pages don't always return Strings, for example if `before_render`
|
15
|
+
# returns a redirect.
|
16
|
+
#
|
17
|
+
# When testing a component, call {#render_and_parse} instead of this. When testing a page that will
|
18
|
+
# always render HTML, again call {#render_and_parse}.
|
19
|
+
#
|
20
|
+
# When using this, there are some matchers that can help assert what the page has done:
|
21
|
+
#
|
22
|
+
# * `have_redirected_to` to check that the page redirected elsewhere, instead of rendering.
|
23
|
+
# * `have_returned_http_status` to check that the page returned an HTTP status instead of rendering.
|
16
24
|
def render(component,&block)
|
17
25
|
if component.kind_of?(Brut::FrontEnd::Page)
|
18
26
|
if !block.nil?
|
@@ -30,7 +38,13 @@ module Brut::SpecSupport::ComponentSupport
|
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
33
|
-
# Render a component and parse it into a Nokogiri Node for examination.
|
41
|
+
# Render a component or page and parse it into a Nokogiri Node for examination. There are several matchers
|
42
|
+
# you can use with the return value of this method:
|
43
|
+
#
|
44
|
+
# * `have_html_attribute` to check if a node has a value for an HTML attribute.
|
45
|
+
# * `have_i18n_string` to check if the text of a node is exactly an i18n string you have set up.
|
46
|
+
# * `have_link_to` to check that a node contains a link to a page or page routing
|
47
|
+
#
|
34
48
|
#
|
35
49
|
# @example
|
36
50
|
#
|
@@ -5,6 +5,9 @@ module Brut::SpecSupport::GeneralSupport
|
|
5
5
|
end
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
+
# Used to indicate that a test does need to be written, but that
|
9
|
+
# its implementation can wait until a given date before causing
|
10
|
+
# `bin/ci` to fail the test suite.
|
8
11
|
def implementation_is_needed(check_again_at:)
|
9
12
|
check_again_at = if check_again_at.kind_of?(Time)
|
10
13
|
check_again_at
|
@@ -2,7 +2,12 @@ require_relative "flash_support"
|
|
2
2
|
require_relative "clock_support"
|
3
3
|
require_relative "session_support"
|
4
4
|
|
5
|
-
# Convienience methods for testing handlers.
|
5
|
+
# Convienience methods for testing handlers. When testing handlers, the following matchers may be useful:
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# * `have_redirected_to` to check that the handler redirected to a give URI
|
9
|
+
# * `have_rendered` to check that the handler rendered a specific page
|
10
|
+
# * `have_returned_http_status` to check that the handler returned an HTTP status
|
6
11
|
module Brut::SpecSupport::HandlerSupport
|
7
12
|
include Brut::SpecSupport::FlashSupport
|
8
13
|
include Brut::SpecSupport::ClockSupport
|
@@ -10,4 +10,5 @@ require_relative "matchers/have_i18n_string"
|
|
10
10
|
require_relative "matchers/have_redirected_to"
|
11
11
|
require_relative "matchers/have_rendered"
|
12
12
|
require_relative "matchers/have_returned_http_status"
|
13
|
+
require_relative "matchers/have_returned_rack_response"
|
13
14
|
require_relative "matchers/have_link_to"
|
@@ -1,9 +1,6 @@
|
|
1
|
+
# Component/Page
|
1
2
|
RSpec::Matchers.define :have_i18n_string do |key,**args|
|
2
|
-
include Brut::I18n::
|
3
|
-
|
4
|
-
# XXX: Figure out how to not have to do this
|
5
|
-
def safe(x) = x
|
6
|
-
def capture(&block) = block.()
|
3
|
+
include Brut::I18n::ForBackEnd
|
7
4
|
|
8
5
|
match do |nokogiri_node|
|
9
6
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Handler
|
2
|
+
RSpec::Matchers.define :have_returned_rack_response do |http_status: :any, headers: :any, body: :any|
|
3
|
+
match do |result|
|
4
|
+
case result
|
5
|
+
in [ response_status, response_headers, response_body ]
|
6
|
+
http_status_match = http_status == :any || response_status == http_status
|
7
|
+
headers_match = headers == :any || response_headers == headers
|
8
|
+
body_match = body == :any || response_body == body
|
9
|
+
|
10
|
+
http_status_match && headers_match && body_match
|
11
|
+
else
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
failure_message do |result|
|
17
|
+
case result
|
18
|
+
in [ response_status, response_headers, response_body ]
|
19
|
+
http_status_match = http_status == :any || response_status == http_status
|
20
|
+
headers_match = headers == :any || response_headers == headers
|
21
|
+
body_match = body == :any || response_body == body
|
22
|
+
errors = [
|
23
|
+
http_status_match ? nil : "HTTP status #{response_status} did not match #{http_status}",
|
24
|
+
headers_match ? nil : "Headers #{response_headers} did not match #{headers}",
|
25
|
+
body_match ? nil : "Body #{response_body} did not match #{body}",
|
26
|
+
].compact.join(", ")
|
27
|
+
else
|
28
|
+
if result.kind_of?(Array)
|
29
|
+
"Response was a #{result.class} of length #{result.length}, which could not be interpreted as a Rack response."
|
30
|
+
else
|
31
|
+
"Response was a #{result.class}, which could not be interpreted as a Rack response."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
failure_message_when_negated do |result|
|
36
|
+
case result
|
37
|
+
in [ response_status, response_headers, response_body ]
|
38
|
+
"Response was a Rack response and/or array of size 3"
|
39
|
+
else
|
40
|
+
"failure_message_when_negated encounterd a code-path for a non-Rack response, which should not have happened when have_returned_rack_response was negated"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/lib/brut/spec_support.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Brut
|
2
2
|
# Spec Support holds various matchers and helpers useful when writing tests with RSpec.
|
3
3
|
# Note that this module and it's contents aren't loaded by default when you `require "brut"`.
|
4
|
-
# Your app's `spec_helper.rb` should require these
|
4
|
+
# Your app's `spec_helper.rb` should require these explicitly.
|
5
5
|
module SpecSupport
|
6
6
|
end
|
7
7
|
end
|
data/lib/brut/version.rb
CHANGED
data/lib/brut.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require_relative "brut/framework"
|
2
2
|
|
3
|
-
# Brut is a way to make web apps with Ruby. It focuses on web standards, object-orientation, and other
|
4
|
-
# minimize abstractions where possible.
|
3
|
+
# Brut is a way to make web apps with Ruby. It focuses on web standards, object-orientation, and other
|
4
|
+
# fundamentals. Brut seeks to minimize abstractions where possible.
|
5
5
|
#
|
6
|
-
# Brut encourages the use of the browser's technology and encourages you to build a web app based
|
7
|
-
# default. Brut may not look easy, but it aims to be simple.
|
6
|
+
# Brut encourages the use of the browser's technology and encourages you to build a web app based
|
7
|
+
# on good practices that are set up by default. Brut may not look easy, but it aims to be simple.
|
8
|
+
# It attempts to minimize dependencies and complexity, while leveraging
|
8
9
|
# common tested Ruby libraries related to web development.
|
9
10
|
#
|
10
11
|
# Have fun!
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.21
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Bryant Copeland
|
@@ -406,14 +406,6 @@ files:
|
|
406
406
|
- bin/rake
|
407
407
|
- bin/setup
|
408
408
|
- brut.gemspec
|
409
|
-
- doc-src/architecture.md
|
410
|
-
- doc-src/assets.md
|
411
|
-
- doc-src/forms.md
|
412
|
-
- doc-src/handlers.md
|
413
|
-
- doc-src/javascript.md
|
414
|
-
- doc-src/keyword-injection.md
|
415
|
-
- doc-src/pages.md
|
416
|
-
- doc-src/route-hooks.md
|
417
409
|
- docker-compose.dx.yml
|
418
410
|
- docs-todo.md
|
419
411
|
- dx/build
|
@@ -553,6 +545,7 @@ files:
|
|
553
545
|
- lib/brut/spec_support/matchers/have_redirected_to.rb
|
554
546
|
- lib/brut/spec_support/matchers/have_rendered.rb
|
555
547
|
- lib/brut/spec_support/matchers/have_returned_http_status.rb
|
548
|
+
- lib/brut/spec_support/matchers/have_returned_rack_response.rb
|
556
549
|
- lib/brut/spec_support/rspec_setup.rb
|
557
550
|
- lib/brut/spec_support/session_support.rb
|
558
551
|
- lib/brut/version.rb
|