brut 0.0.26 → 0.0.27
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/brutrb.com/.vitepress/config.mjs +1 -0
- data/brutrb.com/ai.md +6 -1
- data/brutrb.com/components.md +1 -1
- data/brutrb.com/forms.md +22 -26
- data/brutrb.com/getting-started.md +56 -21
- data/brutrb.com/lsp.md +23 -0
- data/lib/brut/cli/apps/test.rb +2 -1
- data/lib/brut/front_end/component.rb +33 -9
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +16 -74
- data/lib/brut/front_end/forms/constraint_violation.rb +2 -18
- data/lib/brut/front_end/forms/input.rb +8 -8
- data/lib/brut/front_end/forms/input_declarations.rb +3 -3
- data/lib/brut/front_end/forms/radio_button_group_input.rb +1 -1
- data/lib/brut/front_end/forms/select_input.rb +1 -1
- data/lib/brut/front_end/forms/validity_state.rb +23 -0
- data/lib/brut/spec_support/handler_support.rb +1 -1
- data/lib/brut/spec_support/matcher.rb +1 -1
- data/lib/brut/spec_support/matchers/be_a_bug.rb +11 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +10 -0
- data/lib/brut/spec_support/matchers/be_routing_for.rb +18 -0
- data/lib/brut/spec_support/matchers/have_constraint_violation.rb +10 -0
- data/lib/brut/spec_support/matchers/have_generated.rb +28 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +10 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +13 -0
- data/lib/brut/spec_support/matchers/have_link_to.rb +15 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +23 -0
- data/lib/brut/spec_support/matchers/have_returned_http_status.rb +20 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +22 -0
- data/lib/brut/version.rb +1 -1
- metadata +3 -2
- data/lib/brut/spec_support/matchers/have_rendered.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad36cf0ea0c628d6ab48dafc53dfb9253f19ac5df73669e86532970ea70e759d
|
4
|
+
data.tar.gz: fa4dcb2b80e205fb2de2372fe8cfdfd441b6634ab69458287e41f5e681cc507f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a357cccfd1224fcd00d7909670d026a532c1d8cdc0bdd2549a92fad1621370bbdcde707b8fdab0c003ddbe71728018aef2c45201194614025f1b97520775847e
|
7
|
+
data.tar.gz: 042fc9088726c05c0f12b573f54eeb2d7ace79a4552a5756170fb776eaddf5ad895d2a702f1b2e5f05d94f5ff07b7ca4b560e9c3729153441458aa6c3c8e2340
|
data/Gemfile.lock
CHANGED
data/brutrb.com/ai.md
CHANGED
@@ -58,7 +58,7 @@ The logo is level 4 - ChatGPT created it. I'd love to have a real person make a
|
|
58
58
|
something to get this launched. Please reach out if you want to make a better one + other assets. I'm
|
59
59
|
willing to pay a real person.
|
60
60
|
|
61
|
-
## AI
|
61
|
+
## AI Completions Should Be Viewed with Skepticism
|
62
62
|
|
63
63
|
Due to how LLMs work, there is naturally nothing in any model about Brut. Anything an
|
64
64
|
existing model tells you about Brut is **100% untrustworthy**. We hope to allow LLMs to consume Brut's
|
@@ -66,3 +66,8 @@ code, documentation, and examples, so it can be an additional source of help, bu
|
|
66
66
|
the case.
|
67
67
|
|
68
68
|
**Do not ask an LLM about Brut** until this part of the documentation changes.
|
69
|
+
|
70
|
+
For completion-based AI suggestions, **view them with skepticism**. In my
|
71
|
+
experience, completions from e.g. GitHub CoPilot work OK when replicating a pattern
|
72
|
+
in the file you are editing, but suggestions in a freshly-opened file tend to be
|
73
|
+
entirely imaginary or Rails-based. Beware.
|
data/brutrb.com/components.md
CHANGED
@@ -136,7 +136,7 @@ To use it without having to instantiate it, call `global_component` with the com
|
|
136
136
|
class HomePage < AppPage
|
137
137
|
def page_template
|
138
138
|
header do
|
139
|
-
|
139
|
+
global_component(FlashMessage) # note: render not required
|
140
140
|
end
|
141
141
|
end
|
142
142
|
end
|
data/brutrb.com/forms.md
CHANGED
@@ -87,7 +87,7 @@ class LoginPage < AppPage
|
|
87
87
|
end
|
88
88
|
```
|
89
89
|
|
90
|
-
Brut can generate the HTML for the needed inputs via `Brut::FrontEnd::Components::Inputs::TextField
|
90
|
+
Brut can generate the HTML for the needed inputs via `Brut::FrontEnd::Components::Inputs::TextField`, which is a very long class name. Hold that thought for now. This method will generate an `<input>` element for you, based on how you've set up the field in your form class. The HTML element will have a value set based on the form, if there is a value.
|
91
91
|
|
92
92
|
```ruby {11,12}
|
93
93
|
# app/src/front_end/pages/login_page.rb
|
@@ -100,8 +100,8 @@ class LoginPage < AppPage
|
|
100
100
|
form_tag(method: :post,
|
101
101
|
action: LoginHandler.routing) do
|
102
102
|
# We promise you don't have to type this every time!
|
103
|
-
Brut::FrontEnd::Components::Inputs::TextField.
|
104
|
-
Brut::FrontEnd::Components::Inputs::TextField.
|
103
|
+
Brut::FrontEnd::Components::Inputs::TextField.new(form: @form, input_name: :email)
|
104
|
+
Brut::FrontEnd::Components::Inputs::TextField.new(form: @form, input_name: :password)
|
105
105
|
button { "Login" }
|
106
106
|
end
|
107
107
|
end
|
@@ -134,7 +134,7 @@ class LoginPage < AppPage
|
|
134
134
|
end
|
135
135
|
```
|
136
136
|
|
137
|
-
This allows you to call `Inputs::TextField
|
137
|
+
This allows you to call `Inputs::TextField` like a method:
|
138
138
|
|
139
139
|
```ruby {12,13}
|
140
140
|
# app/src/front_end/pages/login_page.rb
|
@@ -148,8 +148,8 @@ class LoginPage < AppPage
|
|
148
148
|
def page_template
|
149
149
|
form_tag(method: :post,
|
150
150
|
action: LoginHandler.routing) do
|
151
|
-
Inputs::TextField
|
152
|
-
Inputs::TextField
|
151
|
+
Inputs::TextField(form: @form, input_name: :email)
|
152
|
+
Inputs::TextField(form: @form, input_name: :password)
|
153
153
|
button { "Login" }
|
154
154
|
end
|
155
155
|
end
|
@@ -217,7 +217,7 @@ with that email and password. Let's assume the existence of the class `Authorize
|
|
217
217
|
If that returns `nil`, we want to re-render the `LoginPage`, exposing some sort of constraint violation message
|
218
218
|
so it can be rendered. We also want the form fields to be pre-filled with the values the visitor provided.
|
219
219
|
|
220
|
-
`
|
220
|
+
`Brut::FrontEnd::Components` can handle this, so we need to pass our form object into `LoginPage` instead of allowing `LoginPage` to create an empty one. We can do that by adding a `form:` keyword argument that defaults to `nil`:
|
221
221
|
|
222
222
|
```ruby {3,4}
|
223
223
|
# app/src/front_end/pages/login_page.rb
|
@@ -229,8 +229,8 @@ class LoginPage < AppPage
|
|
229
229
|
def page_template
|
230
230
|
form_tag(method: :post,
|
231
231
|
action: LoginHandler.routing) do
|
232
|
-
Inputs::TextField
|
233
|
-
Inputs::TextField
|
232
|
+
Inputs::TextField(form: @form, input_name: :email)
|
233
|
+
Inputs::TextField(form: @form, input_name: :password)
|
234
234
|
button { "Login" }
|
235
235
|
end
|
236
236
|
end
|
@@ -267,12 +267,11 @@ class LoginHandler < AppHandler
|
|
267
267
|
end
|
268
268
|
```
|
269
269
|
|
270
|
-
When `LoginPage` generates HTML, different HTML is generated, since the form being passed to
|
271
|
-
`for_form_input` contains constraint violations.
|
270
|
+
When `LoginPage` generates HTML, different HTML is generated, since the form being passed to the components contains constraint violations.
|
272
271
|
|
273
272
|
#### Showing Constraint Violations in HTML
|
274
273
|
|
275
|
-
When `Inputs::TextField
|
274
|
+
When `Brut::FrontEnd::Components::Inputs::TextField` is created with an existing form that has constraint violations, different HTML is generated. This is what would be produced by our existing `LoginPage` (again, formatted her for clarity):
|
276
275
|
|
277
276
|
```html {3}
|
278
277
|
<form method="post" action="/login">
|
@@ -335,10 +334,10 @@ def page_template
|
|
335
334
|
form_tag(method: :post,
|
336
335
|
action: LoginHandler.routing) do
|
337
336
|
|
338
|
-
Inputs::TextField
|
337
|
+
Inputs::TextField(form: @form, input_name: :email)
|
339
338
|
ConstraintViolations(form: @form, input_name: :email)
|
340
339
|
|
341
|
-
Inputs::TextField
|
340
|
+
Inputs::TextField(form: @form, input_name: :password)
|
342
341
|
ConstraintViolations(form: @form, input_name: :password)
|
343
342
|
|
344
343
|
button { "Login" }
|
@@ -419,10 +418,10 @@ def page_template
|
|
419
418
|
form_tag(method: :post,
|
420
419
|
action: LoginHandler.routing) do
|
421
420
|
|
422
|
-
Inputs::TextField
|
421
|
+
Inputs::TextField(form: @form, input_name: :email)
|
423
422
|
ConstraintViolations(form: @form, input_name: :email)
|
424
423
|
|
425
|
-
Inputs::TextField
|
424
|
+
Inputs::TextField(form: @form, input_name: :password)
|
426
425
|
ConstraintViolations(form: @form, input_name: :password)
|
427
426
|
|
428
427
|
button { "Login" }
|
@@ -564,7 +563,7 @@ class LoginForm < AppForm
|
|
564
563
|
end
|
565
564
|
```
|
566
565
|
|
567
|
-
Checkboxes can be rendered by `Inputs::TextField
|
566
|
+
Checkboxes can be rendered by a `Brut::FrontEnd::Components::Inputs::TextField`, and their `value` attribute would always be the string `"true"`. If the form's value for the input is the string `"true"`, the checkbox would have the `checked` attribute:
|
568
567
|
|
569
568
|
```html
|
570
569
|
<!-- Form.new(params: { remember: "true" }) -->
|
@@ -578,8 +577,7 @@ Radio buttons are implemented in HTML by `<input type="radio">`, with an expecta
|
|
578
577
|
having the same value for the `name` attribute, but different values for the `value` attributes, one of which may
|
579
578
|
be `checked`.
|
580
579
|
|
581
|
-
Brut implements this via `Brut::FrontEnd::Components::Inputs::RadioButton`,
|
582
|
-
`for_form_input`. To create radio buttons in a form, use `radio_button_group`:
|
580
|
+
Brut implements this via `Brut::FrontEnd::Components::Inputs::RadioButton`, whose initializer behaves like the other form input components. To create radio buttons in a form, use `radio_button_group`:
|
583
581
|
|
584
582
|
```ruby {5}
|
585
583
|
# app/src/front_end/forms/login_form.rb
|
@@ -598,7 +596,7 @@ def view_template
|
|
598
596
|
[ :never, :one_week, :one_month ].each do |remember|
|
599
597
|
label do
|
600
598
|
render(
|
601
|
-
Inputs::RadioButton
|
599
|
+
Inputs::RadioButton(
|
602
600
|
form:,
|
603
601
|
input_name: :remember,
|
604
602
|
value: remember
|
@@ -643,8 +641,7 @@ class LoginForm < AppForm
|
|
643
641
|
end
|
644
642
|
```
|
645
643
|
|
646
|
-
Creating the HTML can be done with `Brut::FrontEnd::Components::Inputs::Select`. It's
|
647
|
-
as well as to allow for a "blank" entry.
|
644
|
+
Creating the HTML can be done with `Brut::FrontEnd::Components::Inputs::Select`. It's initializer is more complex, since it provides a way to show visitor-friendly values instead of the innate `value` for each option, as well as to allow for a "blank" entry.
|
648
645
|
|
649
646
|
Let's suppose we have a class named `LoginRememberOption`. It's a simple wrapper around a value we might store in the database and use to lookup an I18n key.
|
650
647
|
|
@@ -677,7 +674,7 @@ To show these options in a `<select>`, we might do this:
|
|
677
674
|
def view_template
|
678
675
|
form do
|
679
676
|
render(
|
680
|
-
Inputs::Select
|
677
|
+
Inputs::Select(
|
681
678
|
form:,
|
682
679
|
input_name: :remember,
|
683
680
|
options: LoginRememberOption.all,
|
@@ -718,8 +715,7 @@ end
|
|
718
715
|
|
719
716
|
In this case, we need `required: false` or every single field we generate will be required.
|
720
717
|
|
721
|
-
To generate the HTML, use the optional `index:` parameter to
|
722
|
-
`ConstraintViolations`:
|
718
|
+
To generate the HTML, use the optional `index:` parameter to the initializer as well as for `ConstraintViolations`:
|
723
719
|
|
724
720
|
```ruby {11,16}
|
725
721
|
# Inside e.g. app/src/front_end/pages/create_bulk_widget_page.rb
|
@@ -729,7 +725,7 @@ def page_template
|
|
729
725
|
action: BulkWidgetForm.routing) do
|
730
726
|
|
731
727
|
10.times do |i|
|
732
|
-
Inputs::TextField
|
728
|
+
Inputs::TextField(
|
733
729
|
form: @form,
|
734
730
|
input_name: :name,
|
735
731
|
index: i
|
@@ -1,46 +1,51 @@
|
|
1
1
|
# Getting Started
|
2
2
|
|
3
|
-
|
3
|
+
Brut is developed alongside a separate gem called `mkbrut`, which allows you to
|
4
|
+
create a new Brut app. It will set up you dev environment as well.
|
4
5
|
|
5
|
-
##
|
6
|
+
## Get `mkbrut`
|
6
7
|
|
7
|
-
|
8
|
+
If you have a Ruby 3.4 (or later) environment set up on your computer, you can use
|
9
|
+
RubyGems:
|
8
10
|
|
9
11
|
```
|
10
|
-
|
12
|
+
gem install mkbrut
|
13
|
+
```
|
14
|
+
|
15
|
+
If not, we recommend you use a pre-built Docker image:
|
16
|
+
|
17
|
+
```
|
18
|
+
docker pull XXXX
|
11
19
|
```
|
12
20
|
|
13
21
|
## Init Your App
|
14
22
|
|
15
|
-
|
23
|
+
A Brut app just needs a name, which will be used to derive a few more useful values.
|
24
|
+
For now:
|
16
25
|
|
17
26
|
```
|
18
|
-
|
19
|
-
> ./init
|
27
|
+
mkbrut my-new-app
|
20
28
|
```
|
21
29
|
|
22
|
-
|
30
|
+
This will create your new app, along with some demo routes, components, handlers, and tests. If this is your first time using Brut, we recommend you examine these demo components. However, if you just want to skip all that:
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
* An organization name, needed for deployment
|
28
|
-
|
29
|
-
::: tip
|
30
|
-
Choose your app's name wisely, however everything else can be easily changed later, so don't stress!
|
31
|
-
:::
|
32
|
+
```
|
33
|
+
mkbrut --no-demo my-new-app
|
34
|
+
```
|
32
35
|
|
33
|
-
##
|
36
|
+
## Start Your Dev Environment
|
34
37
|
|
35
|
-
Brut includes a dev environment based on Docker.
|
38
|
+
Brut includes a dev environment based on Docker. It uses Docker compose to run a
|
39
|
+
Docker container where your app will run, a Docker container for Postgres, and a
|
40
|
+
Docker container for local observability via OpenTelemetry.
|
36
41
|
|
37
42
|
1. [Install Docker](https://docs.docker.com/get-started/get-docker/)
|
38
|
-
2. Build
|
43
|
+
2. Build the image used to create you app's container:
|
39
44
|
|
40
45
|
```
|
41
46
|
> dx/build
|
42
47
|
```
|
43
|
-
3. Start up the
|
48
|
+
3. Start up all the containers:
|
44
49
|
|
45
50
|
```
|
46
51
|
> dx/start
|
@@ -51,6 +56,13 @@ Brut includes a dev environment based on Docker.
|
|
51
56
|
> dx/exec bin/setup
|
52
57
|
```
|
53
58
|
|
59
|
+
OR:
|
60
|
+
|
61
|
+
```
|
62
|
+
> dx/exec bash
|
63
|
+
inside-container> bin/setup
|
64
|
+
```
|
65
|
+
|
54
66
|
Now, you're ready to go
|
55
67
|
|
56
68
|
## Run the App
|
@@ -59,8 +71,31 @@ Now, you're ready to go
|
|
59
71
|
> dx/exec bin/dev
|
60
72
|
```
|
61
73
|
|
74
|
+
OR
|
75
|
+
|
76
|
+
```
|
77
|
+
> dx/exec bash
|
78
|
+
> bin/dev
|
79
|
+
```
|
80
|
+
|
62
81
|
You can now visit your app at `localhost:6502`
|
63
82
|
|
83
|
+
## Run the Tests
|
84
|
+
|
85
|
+
Even without the demo, there are a few components set up, and there are some tests:
|
86
|
+
|
87
|
+
```
|
88
|
+
> dx/exec bin/ci
|
89
|
+
```
|
90
|
+
|
91
|
+
OR
|
92
|
+
|
93
|
+
```
|
94
|
+
> dx/exec bash
|
95
|
+
> bin/ci
|
96
|
+
```
|
97
|
+
|
64
98
|
## Now Build The Rest of Your App 🦉
|
65
99
|
|
66
|
-
You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs.
|
100
|
+
You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs. You might also want to check out the docs for [LSP Support](/lsp).
|
101
|
+
|
data/brutrb.com/lsp.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Language Server Protocol (LSP) Support
|
2
|
+
|
3
|
+
Because Brut development happens inside Docker, but your editor likely runs on your
|
4
|
+
computer, getting LSP servers running takes a few more steps.
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
When you created your app with `mkbrut`, the following LSP-related modules are set
|
9
|
+
up and/or installed:
|
10
|
+
|
11
|
+
* Shopify's Ruby LSP server (installed from `bin/setup`)
|
12
|
+
* Microsoft's TypeScript/JavaScript and CSS LSP serfvers (specified in `package.json`, installed when `npm install` runs from `bin/setup`)
|
13
|
+
|
14
|
+
In order to use them from your computer a few configurations are needed, some of
|
15
|
+
which Brut has done, and some you will need to do.
|
16
|
+
|
17
|
+
| Configuration | Description | Brut Handled? |
|
18
|
+
|---|---|---|
|
19
|
+
| Paths inside Docker Must Match Your Computer | When an LSP server communicates about a file, it does so with a path. That means that paths inside the Docker container must be the same as those on your computer. Brut achievecs this by using `${CWD}` inside `docker-compose.dx.yml` | ✅ |
|
20
|
+
| Third party libraries must *also* be installed in a path that is the same in both places | When jumping to a definition, the LSP server will again use paths, which must match. Because Node modules are installed local to your app, they already work. Ruby Gems, however, are configured to be installed in `local-gems` in your app. Brut should've added this to `.gitignore` and setup everything inside Docker to use it. | ✅ |
|
21
|
+
| Your editor must use `dx/exec` to execute LSP commands | Your editor will need to know that the LSP servers are running inside Docker. If your editor allows configuring the commands used to do this, you must prefix them with `dx/exec bashc -lc`. See [my blog post](https://naildrivin5.com/blog/2025/06/12/neovim-and-lsp-servers-working-with-docker-based-development.html) for details. | ❌ |
|
22
|
+
| Other languages or plugins to existing LSP servers | I haven't used these, so no idea how well they work. | ❌ |
|
23
|
+
|
data/lib/brut/cli/apps/test.rb
CHANGED
@@ -133,7 +133,8 @@ class Brut::CLI::Apps::Test < Brut::CLI::App
|
|
133
133
|
test_expected: true,
|
134
134
|
}
|
135
135
|
if pathname.fnmatch?( (Brut.container.components_src_dir / "**").to_s )
|
136
|
-
if pathname.basename.to_s == "app_component.rb"
|
136
|
+
if pathname.basename.to_s == "app_component.rb" ||
|
137
|
+
pathname.basename.to_s == "custom_element_registration.rb"
|
137
138
|
hash[:type] = :infrastructure
|
138
139
|
hash[:test_expected] = false
|
139
140
|
else
|
@@ -1,6 +1,22 @@
|
|
1
1
|
require "phlex"
|
2
2
|
|
3
|
-
#
|
3
|
+
# Namespace for Brut-provided components that are of general use to any web app.
|
4
|
+
# Also extends [`Phlex:::Kit`](https://www.phlex.fun/components/kits.html), meaning
|
5
|
+
# you can include this module in your pages and components to be able to
|
6
|
+
# create Brut's components without `.new` or without the full classname:
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class AppPage < Brut::FrontEnd::Page
|
10
|
+
# include Brut::FrontEnd::Components
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# class HomePage < AppPage
|
14
|
+
# def page_template
|
15
|
+
# h1 do
|
16
|
+
# span { "It's }
|
17
|
+
# TimeTag(timestamp: Time.now)
|
18
|
+
# end
|
19
|
+
# end
|
4
20
|
module Brut::FrontEnd::Components
|
5
21
|
autoload(:FormTag,"brut/front_end/components/form_tag")
|
6
22
|
autoload(:Input,"brut/front_end/components/input")
|
@@ -72,16 +88,24 @@ class Brut::FrontEnd::Component < Phlex::HTML
|
|
72
88
|
}
|
73
89
|
end
|
74
90
|
|
75
|
-
#
|
76
|
-
# This
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
91
|
+
# Render (in Phlex parlance) a component that you would like Brut to
|
92
|
+
# instantiate. This is useful when you want to use a component that
|
93
|
+
# only requires values from the {Brut::FrontEnd::RequestContext}. By
|
94
|
+
# using this method, *this* component does not have to receive
|
95
|
+
# data from the {Brut::FrontEnd::RequestContext} that only serves to pass
|
96
|
+
# to the component you use here.
|
97
|
+
#
|
98
|
+
# For example, you may have a component that renders the flash message. To avoid requiring *this* component/page to be passed the flash, a global component can be injected with it from Brut.
|
99
|
+
#
|
100
|
+
# This component *will* be rendered into the Phlex context. Do not call
|
101
|
+
# `render` on the result, nor rely on the return value.
|
80
102
|
#
|
81
|
-
# @
|
82
|
-
#
|
103
|
+
# @param [Class] component_klass the component class to use in the view.
|
104
|
+
# This class's
|
105
|
+
# initializer must only require information available from the
|
106
|
+
# {Brut::FrontEnd::RequestContext}.
|
83
107
|
def global_component(component_klass)
|
84
|
-
Brut::FrontEnd::RequestContext.inject(component_klass)
|
108
|
+
render Brut::FrontEnd::RequestContext.inject(component_klass)
|
85
109
|
end
|
86
110
|
end
|
87
111
|
include Helpers
|
@@ -27,14 +27,14 @@ class Brut::FrontEnd::Components::Inputs::SelectTagWithOptions < Brut::FrontEnd:
|
|
27
27
|
# to be used as the `value` attribute and option text content, respectively.
|
28
28
|
#
|
29
29
|
# @return [Brut::FrontEnd::Components::Inputs::SelectTagWithOptions] the select input ready to be placed into a view.
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
def initialize(form:,
|
31
|
+
input_name:,
|
32
|
+
options:,
|
33
|
+
include_blank: false,
|
34
|
+
value_attribute:,
|
35
|
+
option_text_attribute:,
|
36
|
+
index: nil,
|
37
|
+
html_attributes: {})
|
38
38
|
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
39
39
|
default_html_attributes = {}
|
40
40
|
index ||= 0
|
@@ -54,84 +54,26 @@ class Brut::FrontEnd::Components::Inputs::SelectTagWithOptions < Brut::FrontEnd:
|
|
54
54
|
input.name
|
55
55
|
end
|
56
56
|
|
57
|
-
|
58
|
-
name: name,
|
59
|
-
options:,
|
60
|
-
selected_value: input.value,
|
61
|
-
value_attribute:,
|
62
|
-
option_text_attribute:,
|
63
|
-
include_blank:,
|
64
|
-
html_attributes: default_html_attributes.merge(html_attributes)
|
65
|
-
)
|
66
|
-
end
|
57
|
+
input_value = input.value
|
67
58
|
|
68
|
-
# Create the element.
|
69
|
-
#
|
70
|
-
# @param [String] name the name of the input
|
71
|
-
# @param [Array<Object>] options An array of objects represented what is being selected.
|
72
|
-
# These can be any object and are ideally whatever domain object or
|
73
|
-
# data type you want on the backend to represent this selection.
|
74
|
-
# @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
|
75
|
-
# This will be called on each element of `options` to get the value used for the `<option>`'s
|
76
|
-
# `value` attribute. The value returned by `value_attribute` should be unique amongst the
|
77
|
-
# `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
|
78
|
-
# @param [String] selected_value the value of the selected option. Note that this is the *value*
|
79
|
-
# of the selected option, not the selected option itself. To set the selected value
|
80
|
-
# based on a selected option, omit this and use `selected_option`
|
81
|
-
# @param [String] selected_option the selected option. Note that `value_attribute` will be called
|
82
|
-
# on this to determine the selected value to use when generating HTML. Also note that
|
83
|
-
# this object must be in `options` or an exeception is raised.
|
84
|
-
# @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
|
85
|
-
# This will be called on each element of `options` to get the value used for the `<option>`'s
|
86
|
-
# text content. The value returned by `option_text_attribute` need not be unique, though if it
|
87
|
-
# is not unique, it will certainly be confusing.
|
88
|
-
# @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
|
89
|
-
# @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
|
90
|
-
# If this is false, there will be no blank element. If it's `true`, there will be one with
|
91
|
-
# no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
|
92
|
-
# to be used as the `value` attribute and option text content, respectively.
|
93
|
-
#
|
94
|
-
# @raise ArgumentError if `selected_option` is present, but not in `options` or if `selected_value` is
|
95
|
-
# present, but no option's value for `value_attribute` is that value.
|
96
|
-
#
|
97
|
-
# XXX: Why does this not ask the form for the selected_value?
|
98
|
-
# XXX: This doesn't do well when values are strings
|
99
|
-
def initialize(name:,
|
100
|
-
options:,
|
101
|
-
value_attribute:,
|
102
|
-
selected_value: nil,
|
103
|
-
selected_option: nil,
|
104
|
-
option_text_attribute:,
|
105
|
-
include_blank: false,
|
106
|
-
html_attributes:)
|
107
59
|
@options = options
|
108
60
|
@include_blank = IncludeBlank.from_param(include_blank)
|
109
61
|
@value_attribute = value_attribute
|
110
62
|
@option_text_attribute = option_text_attribute
|
111
|
-
@html_attributes = html_attributes
|
63
|
+
@html_attributes = default_html_attributes.merge(html_attributes)
|
112
64
|
@html_attributes[:name] = name
|
113
65
|
|
114
|
-
if
|
115
|
-
|
116
|
-
@selected_value = nil # explicitly nothing is selected
|
117
|
-
else
|
118
|
-
option = options.detect { |option|
|
119
|
-
option.send(@value_attribute) == selected_option.send(@value_attribute)
|
120
|
-
}
|
121
|
-
if option.nil?
|
122
|
-
raise ArgumentError, "selected_option #{selected_option} (with #{value_attribute} '#{selected_option.send(value_attribute)}') was not found in options"
|
123
|
-
end
|
124
|
-
@selected_value = option.send(@value_attribute)
|
125
|
-
end
|
66
|
+
if input_value.nil?
|
67
|
+
@selected_value = nil # explicitly nothing is selected
|
126
68
|
else
|
127
|
-
if
|
128
|
-
raise "WTF: #{name}"
|
69
|
+
if input_value.kind_of?(Array)
|
70
|
+
raise "WTF: #{name}" # XXX?
|
129
71
|
end
|
130
72
|
option = options.detect { |option|
|
131
|
-
|
73
|
+
input_value == option.send(@value_attribute)
|
132
74
|
}
|
133
75
|
if option.nil?
|
134
|
-
raise ArgumentError, "selected_value #{
|
76
|
+
raise ArgumentError, "selected_value #{input_value} was not the value for #{value_attribute} on any of the options: #{options.map { |option| option.send(value_attribute) }.join(', ')}"
|
135
77
|
end
|
136
78
|
@selected_value = option.send(@value_attribute)
|
137
79
|
end
|
@@ -2,22 +2,6 @@
|
|
2
2
|
# form-related classes.
|
3
3
|
class Brut::FrontEnd::Forms::ConstraintViolation
|
4
4
|
|
5
|
-
# These are underscorized versions of the attributes of the browser's `ValidityState`'s properties.
|
6
|
-
#
|
7
|
-
# @see https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
|
8
|
-
CLIENT_SIDE_KEYS = [
|
9
|
-
"bad_input",
|
10
|
-
"custom_error",
|
11
|
-
"pattern_mismatch",
|
12
|
-
"range_overflow",
|
13
|
-
"range_underflow",
|
14
|
-
"step_mismatch",
|
15
|
-
"too_long",
|
16
|
-
"too_short",
|
17
|
-
"type_mismatch",
|
18
|
-
"value_missing",
|
19
|
-
]
|
20
|
-
|
21
5
|
# @return [String] the key fragment representing the violation
|
22
6
|
attr_reader :key
|
23
7
|
# @return [Hash] interpolated values useful in rendering the actual message
|
@@ -27,11 +11,11 @@ class Brut::FrontEnd::Forms::ConstraintViolation
|
|
27
11
|
#
|
28
12
|
# @param [String|Symbol] key I18n key fragment representing this violation.
|
29
13
|
# @param [Hash|nil] context interpolated values useful in rendering the message
|
30
|
-
# @param [true|:based_on_key] server_side If `:based_on_key`, {#client_side?} will return true if `key` is in {.
|
14
|
+
# @param [true|:based_on_key] server_side If `:based_on_key`, {#client_side?} will return true if `key` is in {ValidityState.KEYS}.
|
31
15
|
# If `true`, {#client_side?} will return false no matter what.
|
32
16
|
def initialize(key:,context:, server_side: :based_on_key)
|
33
17
|
@key = key.to_s
|
34
|
-
@client_side =
|
18
|
+
@client_side = Brut::FrontEnd::Forms::ValidityState::KEYS.include?(@key) && server_side != true
|
35
19
|
@context = context || {}
|
36
20
|
if !@context.kind_of?(Hash)
|
37
21
|
raise "#{self.class} created for key #{key} with an invalid context: '#{context}/#{context.class}'. Context must be nil or a hash"
|
@@ -82,14 +82,14 @@ class Brut::FrontEnd::Forms::Input
|
|
82
82
|
step_mismatch = false
|
83
83
|
|
84
84
|
@validity_state = Brut::FrontEnd::Forms::ValidityState.new(
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
85
|
+
valueMissing: missing,
|
86
|
+
tooShort: too_short,
|
87
|
+
tooLong: too_short,
|
88
|
+
rangeOverflow: range_overflow,
|
89
|
+
rangeUnderflow: range_underflow,
|
90
|
+
patternMismatch: pattern_mismatch,
|
91
|
+
stepMismatch: step_mismatch,
|
92
|
+
typeMismatch: type_mismatch,
|
93
93
|
)
|
94
94
|
@value = new_value
|
95
95
|
end
|
@@ -12,8 +12,8 @@ module Brut::FrontEnd::Forms::InputDeclarations
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Declares a select for this form, to be modeled via an HTML `<SELECT>` tag. Note that this will not define the values that appear
|
15
|
-
# in the select. That is done when the select is rendered, which you might do with
|
16
|
-
# {Brut::FrontEnd::Components::Inputs::Select
|
15
|
+
# in the select. That is done when the select is rendered, which you might do with a
|
16
|
+
# {Brut::FrontEnd::Components::Inputs::Select}
|
17
17
|
#
|
18
18
|
# @param [String] name The name of the input (used in the `name` attribute)
|
19
19
|
# @param [Hash] attributes Attributes to be used on the tag that represent its contraints. See {Brut::FrontEnd::Forms::SelectInputDefinition}
|
@@ -28,7 +28,7 @@ module Brut::FrontEnd::Forms::InputDeclarations
|
|
28
28
|
# input tags.
|
29
29
|
#
|
30
30
|
# Note that this is not where you would define the possible values for the group. That is done in
|
31
|
-
# {Brut::FrontEnd::Components::Inputs::RadioButton
|
31
|
+
# {Brut::FrontEnd::Components::Inputs::RadioButton}.
|
32
32
|
#
|
33
33
|
# @param [String] name The name of the group (used in the `name` attribute)
|
34
34
|
# @param [Hash] attributes Attributes to be used on the tag that represent its contraints. See {Brut::FrontEnd::Forms::RadioButtonGroupInputDefinition}
|
@@ -3,6 +3,11 @@
|
|
3
3
|
# In a sense, this is a wrapper for one or more {Brut::FrontEnd::Forms::ConstraintViolation} instances in the
|
4
4
|
# context of an input.
|
5
5
|
#
|
6
|
+
# This class also holds the logic related to client- vs. server-side constraint
|
7
|
+
# violations. As such, {.KEYS} is the list of known client-side
|
8
|
+
# constraint violation keys, as defined by browser's `ValidityState` class. These
|
9
|
+
# are left as camel-case to make this clear.
|
10
|
+
#
|
6
11
|
# @see https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
|
7
12
|
class Brut::FrontEnd::Forms::ValidityState
|
8
13
|
include Enumerable
|
@@ -46,5 +51,23 @@ class Brut::FrontEnd::Forms::ValidityState
|
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
54
|
+
|
55
|
+
# These are the attributes of the browser's `ValidityState`'s properties.
|
56
|
+
# They are left as camel-case to clearly indicate they come from the browser.
|
57
|
+
#
|
58
|
+
# @see https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
|
59
|
+
KEYS = [
|
60
|
+
"badInput",
|
61
|
+
"customError",
|
62
|
+
"patternMismatch",
|
63
|
+
"rangeOverflow",
|
64
|
+
"rangeUnderflow",
|
65
|
+
"stepMismatch",
|
66
|
+
"tooLong",
|
67
|
+
"tooShort",
|
68
|
+
"typeMismatch",
|
69
|
+
"valueMissing",
|
70
|
+
]
|
71
|
+
|
49
72
|
end
|
50
73
|
|
@@ -6,7 +6,7 @@ require_relative "session_support"
|
|
6
6
|
#
|
7
7
|
#
|
8
8
|
# * `have_redirected_to` to check that the handler redirected to a give URI
|
9
|
-
# * `
|
9
|
+
# * `have_generated` to check that the handler rendered a specific page
|
10
10
|
# * `have_returned_http_status` to check that the handler returned an HTTP status
|
11
11
|
module Brut::SpecSupport::HandlerSupport
|
12
12
|
include Brut::SpecSupport::FlashSupport
|
@@ -8,7 +8,7 @@ require_relative "matchers/have_constraint_violation"
|
|
8
8
|
require_relative "matchers/have_html_attribute"
|
9
9
|
require_relative "matchers/have_i18n_string"
|
10
10
|
require_relative "matchers/have_redirected_to"
|
11
|
-
require_relative "matchers/
|
11
|
+
require_relative "matchers/have_generated"
|
12
12
|
require_relative "matchers/have_returned_http_status"
|
13
13
|
require_relative "matchers/have_returned_rack_response"
|
14
14
|
require_relative "matchers/have_link_to"
|
@@ -12,3 +12,14 @@ RSpec::Matchers.define :be_a_bug do
|
|
12
12
|
|
13
13
|
supports_block_expectations
|
14
14
|
end
|
15
|
+
|
16
|
+
# Block-based matcher that expects the blog to
|
17
|
+
# raise a {Brut::Framework::Errors::Bug}.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# expect {
|
21
|
+
# SomeClass.new(value: :invalid)
|
22
|
+
# }.to be_a_bug
|
23
|
+
#
|
24
|
+
class Brut::SpecSupport::Matchers::BeABug
|
25
|
+
end
|
@@ -14,3 +14,13 @@ RSpec::Matchers.define :be_page_for do |klass|
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
# Matcher for end-to-end tests to assert that the current page
|
19
|
+
# is the page you expect it to be. This is based on the
|
20
|
+
# {Brut::FrontEnd::Components::PageIdentifier} component, which must
|
21
|
+
# be included on the page for this matcher to work.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# expect(page).to be_page_for(HomePage)
|
25
|
+
class Brut::SpecSupport::Matchers::BePageFor
|
26
|
+
end
|
@@ -9,3 +9,21 @@ RSpec::Matchers.define :be_routing_for do |klass,**args|
|
|
9
9
|
end
|
10
10
|
|
11
11
|
end
|
12
|
+
|
13
|
+
# Matcher used for handlers (or any code that returns a `URI`)
|
14
|
+
# to assert that the URI is for a given page with the given set of parameters
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# result = handler.handle
|
18
|
+
# expect(result).to be_routing_for(HomePage)
|
19
|
+
#
|
20
|
+
# @example with parameters
|
21
|
+
# result = handler.handle
|
22
|
+
# expect(result).to be_routing_for(WidgetsByWidgetIdPage, id: widget.external_id)
|
23
|
+
#
|
24
|
+
# @example with anchor
|
25
|
+
# result = handler.handle
|
26
|
+
# expect(result).to be_routing_for(MessagesPage, anchor: "latest_message")
|
27
|
+
#
|
28
|
+
class Brut::SpecSupport::Matchers::BeRoutingFor
|
29
|
+
end
|
@@ -29,6 +29,16 @@ RSpec::Matchers.define :have_constraint_violation do |field,key:,index:nil|
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
# Matcher to check that a from has a specific constraint violation.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# expect(form).to have_constraint_violation(:email, key: :required)
|
36
|
+
#
|
37
|
+
# @example Index fields (requires that the third email field have a constraint violation)
|
38
|
+
# expect(form).to have_constraint_violation(:email, key: :required, index: 2)
|
39
|
+
#
|
40
|
+
# @example Negated
|
41
|
+
# expect(form).not_to have_constraint_violation(:email, key: :required)
|
32
42
|
class Brut::SpecSupport::Matchers::HaveConstraintViolation
|
33
43
|
attr_reader :fields_found
|
34
44
|
attr_reader :keys_on_field_found
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Handler
|
2
|
+
RSpec::Matchers.define :have_generated do |component_or_page|
|
3
|
+
match do |result|
|
4
|
+
result.class.ancestors.include?(component_or_page)
|
5
|
+
end
|
6
|
+
|
7
|
+
failure_message do |result|
|
8
|
+
"Expected a #{component_or_page} to be generated, but got #{result}"
|
9
|
+
end
|
10
|
+
failure_message_when_negated do |result|
|
11
|
+
"Got #{component_or_page} when not expected"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
# Matcher to check that a handler generated a specific page
|
17
|
+
# (as opposed to redirected to a page). This works for components
|
18
|
+
# as well, in the case of Ajax requests.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# result = handler.handle(form:)
|
22
|
+
# expect(result).to have_generated(NewWidgetPage)
|
23
|
+
#
|
24
|
+
# @example Ajax request
|
25
|
+
# result = handler.handle(form:, xhr: true)
|
26
|
+
# expect(result).to have_generated(WidgetResponseComponent)
|
27
|
+
class Brut::SpecSupport::Matchers::HaveGenerated
|
28
|
+
end
|
@@ -25,6 +25,16 @@ RSpec::Matchers.define :have_html_attribute do |attribute|
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
# Used in component or page specs to check that a given node
|
29
|
+
# has a particular HTML attribnute, or an attribute with a speecific value.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# result = generate_and_parse(page)
|
33
|
+
# expect(result.e!("blockquote")).to have_html_attribute(:id)
|
34
|
+
#
|
35
|
+
# @example Expecting a value as well
|
36
|
+
# result = generate_and_parse(page)
|
37
|
+
# expect(result.e!("blockquote")).to have_html_attribute(id: "foo")
|
28
38
|
class Brut::SpecSupport::Matchers::HaveHTMLAttribute
|
29
39
|
|
30
40
|
attr_reader :error
|
@@ -24,3 +24,16 @@ RSpec::Matchers.define :have_i18n_string do |key,**args|
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
# Used in component or page specs to check if a Nokogiri node's
|
28
|
+
# text contains a specific i18n string, without you having
|
29
|
+
# to use `t` to look it up.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# result = generate_and_parse(page)
|
33
|
+
# expect(result.e!("h3")).to have_i18n_string(:greeting)
|
34
|
+
#
|
35
|
+
# @example I18n string with parameters
|
36
|
+
# result = generate_and_parse(page)
|
37
|
+
# expect(result.e!("h3")).to have_i18n_string(:user_greeting, email: account.email)
|
38
|
+
class Brut::SpecSupport::Matchers::HaveI18nString
|
39
|
+
end
|
@@ -13,3 +13,18 @@ RSpec::Matchers.define :have_link_to do |page_klass,**args|
|
|
13
13
|
"Did not expect to find link to #{page_klass.routing(**args)}."
|
14
14
|
end
|
15
15
|
end
|
16
|
+
|
17
|
+
# Used on a component/page spec to check that there is a link
|
18
|
+
# to a specific routing. This handles creating a CSS selector
|
19
|
+
# like `[href="#{page_klass.routing(**args)}"]`.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# result = generate_and_parse(page)
|
23
|
+
# expect(result.e!("nav")).to have_link_to(HomePage)
|
24
|
+
#
|
25
|
+
# @example link with parameters
|
26
|
+
# result = generate_and_parse(page)
|
27
|
+
# expect(result.e!("nav")).to have_link_to(WidgetsByWidgetIdPage, id: widget.id)
|
28
|
+
#
|
29
|
+
class Brut::SpecSupport::Matchers::HaveLinkTo
|
30
|
+
end
|
@@ -37,5 +37,28 @@ RSpec::Matchers.define :have_redirected_to do |page_or_uri,**page_params|
|
|
37
37
|
failure_message_when_negated do |result|
|
38
38
|
"Got a redirect when it wasn't expected"
|
39
39
|
end
|
40
|
+
end
|
40
41
|
|
42
|
+
# Used on handler specs to check that a response has
|
43
|
+
# redirected to a page or URI. Can also be used
|
44
|
+
# with {Brut::SpecSupport::ComponentSupport#generate_result} to
|
45
|
+
# check that a Page's {Brut::FrontEnd::Page#before_generate} method
|
46
|
+
# did what you expect
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# result = handler.handle
|
50
|
+
# expect(result).to have_redirected_to(HomePage)
|
51
|
+
#
|
52
|
+
# @example with parameters
|
53
|
+
# result = handler.handle
|
54
|
+
# expect(result).to have_redirected_to(WidgetsByWidgetIdPage, id: widget.id)
|
55
|
+
#
|
56
|
+
# @example arbitrary URI
|
57
|
+
# result = handler.handle
|
58
|
+
# expect(result).to have_redirected_to("http://kagi.com")
|
59
|
+
#
|
60
|
+
# @example testing a `before_generate` method
|
61
|
+
# result = generate_result(page)
|
62
|
+
# expect(result).to have_redirected_to(LoginPage)
|
63
|
+
class Brut::SpecSupport::Matchers::HaveRedirectedTo
|
41
64
|
end
|
@@ -31,5 +31,25 @@ RSpec::Matchers.define :have_returned_http_status do |http_status=nil|
|
|
31
31
|
"Got #{http_status} when not expected (#{result.class} was returned)"
|
32
32
|
end
|
33
33
|
end
|
34
|
+
end
|
34
35
|
|
36
|
+
# Used on handler specs to check that a response returned
|
37
|
+
# a particular HTTP status code. Can also be used
|
38
|
+
# with {Brut::SpecSupport::ComponentSupport#generate_result} to
|
39
|
+
# check that a Page's {Brut::FrontEnd::Page#before_generate} method
|
40
|
+
# did what you expect
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# result = handler.handle
|
44
|
+
# expect(result).to have_returned_http_status(404)
|
45
|
+
#
|
46
|
+
# @example dont' care what status, just that one was returned instead of a page generation
|
47
|
+
# result = handler.handle
|
48
|
+
# expect(result).to have_returned_http_status
|
49
|
+
#
|
50
|
+
#
|
51
|
+
# @example testing a `before_generate` method
|
52
|
+
# result = generate_result(page)
|
53
|
+
# expect(result).to have_returned_http_status(403)
|
54
|
+
class Brut::SpecSupport::Matchers::HaveReturnedHttpStatus
|
35
55
|
end
|
@@ -42,3 +42,25 @@ RSpec::Matchers.define :have_returned_rack_response do |http_status: :any, heade
|
|
42
42
|
end
|
43
43
|
|
44
44
|
end
|
45
|
+
|
46
|
+
# Used on handler specs to check that a response returned
|
47
|
+
# a Rack response. Can also be used
|
48
|
+
# with {Brut::SpecSupport::ComponentSupport#generate_result} to
|
49
|
+
# check that a Page's {Brut::FrontEnd::Page#before_generate} method
|
50
|
+
# did what you expect
|
51
|
+
#
|
52
|
+
# The matcher expects these keyword arguments:
|
53
|
+
#
|
54
|
+
# * `http_status:` - the expected HTTP status code as a number, or `:any` (the default), if it's not relevant to the test.
|
55
|
+
# * `headers:` - the expected headers as a Hash of Strings to Strings, or `:any` (the default), if they are not relevant to the test.
|
56
|
+
# * `body:` - the expected body, or `:any` (the default), if it is not relevant to the test.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# result = handler.handle
|
60
|
+
# expect(result).to have_returned_rack_response(
|
61
|
+
# http_status: 200,
|
62
|
+
# headers: { "Content-Type" => "text/html" }
|
63
|
+
# )
|
64
|
+
#
|
65
|
+
class Brut::SpecSupport::Matchers::HaveReturnedRackResponse
|
66
|
+
end
|
data/lib/brut/version.rb
CHANGED
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.27
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Bryant Copeland
|
@@ -614,6 +614,7 @@ files:
|
|
614
614
|
- brutrb.com/javascript.md
|
615
615
|
- brutrb.com/jobs.md
|
616
616
|
- brutrb.com/keyword-injection.md
|
617
|
+
- brutrb.com/lsp.md
|
617
618
|
- brutrb.com/markdown-examples.md
|
618
619
|
- brutrb.com/middleware.md
|
619
620
|
- brutrb.com/not-released.md
|
@@ -1182,11 +1183,11 @@ files:
|
|
1182
1183
|
- lib/brut/spec_support/matchers/be_page_for.rb
|
1183
1184
|
- lib/brut/spec_support/matchers/be_routing_for.rb
|
1184
1185
|
- lib/brut/spec_support/matchers/have_constraint_violation.rb
|
1186
|
+
- lib/brut/spec_support/matchers/have_generated.rb
|
1185
1187
|
- lib/brut/spec_support/matchers/have_html_attribute.rb
|
1186
1188
|
- lib/brut/spec_support/matchers/have_i18n_string.rb
|
1187
1189
|
- lib/brut/spec_support/matchers/have_link_to.rb
|
1188
1190
|
- lib/brut/spec_support/matchers/have_redirected_to.rb
|
1189
|
-
- lib/brut/spec_support/matchers/have_rendered.rb
|
1190
1191
|
- lib/brut/spec_support/matchers/have_returned_http_status.rb
|
1191
1192
|
- lib/brut/spec_support/matchers/have_returned_rack_response.rb
|
1192
1193
|
- lib/brut/spec_support/rspec_setup.rb
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# Handler
|
2
|
-
RSpec::Matchers.define :have_rendered do |component_or_page|
|
3
|
-
match do |result|
|
4
|
-
result.class.ancestors.include?(component_or_page)
|
5
|
-
end
|
6
|
-
|
7
|
-
failure_message do |result|
|
8
|
-
"Expected a #{component_or_page} to be rendered, but got #{result}"
|
9
|
-
end
|
10
|
-
failure_message_when_negated do |result|
|
11
|
-
"Got #{component_or_page} when not expected"
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|