view_spec 0.0.0 → 0.0.1
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/LICENSE.txt +1 -1
- data/README.md +267 -24
- data/app/controllers/concerns/view_specs/preview_actions.rb +37 -0
- data/app/controllers/view_spec_previews_controller.rb +3 -0
- data/app/views/view_specs/_group.html.erb +56 -0
- data/app/views/view_specs/_scenario.html.erb +55 -0
- data/app/views/view_specs/_spec.html.erb +18 -0
- data/app/views/view_specs/group/_notes.html.erb +14 -0
- data/app/views/view_specs/group/_preview.html.erb +19 -0
- data/app/views/view_specs/group/_raw.html.erb +7 -0
- data/app/views/view_specs/group/_source.html.erb +7 -0
- data/app/views/view_specs/previews/index.html.erb +22 -0
- data/lib/view_spec/collection.rb +43 -0
- data/lib/view_spec/config.rb +26 -3
- data/lib/view_spec/context.rb +40 -0
- data/lib/view_spec/dsl/controller.rb +7 -2
- data/lib/view_spec/dsl/groups.rb +2 -2
- data/lib/view_spec/dsl/layout.rb +7 -1
- data/lib/view_spec/dsl/notes.rb +22 -0
- data/lib/view_spec/dsl/params.rb +25 -15
- data/lib/view_spec/dsl/preview.rb +25 -0
- data/lib/view_spec/dsl/scenarios.rb +6 -6
- data/lib/view_spec/dsl/tests.rb +18 -0
- data/lib/view_spec/dsl/title.rb +1 -1
- data/lib/view_spec/engine.rb +20 -1
- data/lib/view_spec/entry.rb +62 -0
- data/lib/view_spec/entry_collection.rb +11 -0
- data/lib/view_spec/error.rb +4 -0
- data/lib/view_spec/executable_proc.rb +32 -0
- data/lib/view_spec/executable_string.rb +23 -0
- data/lib/view_spec/group.rb +17 -13
- data/lib/view_spec/group_collection.rb +4 -0
- data/lib/view_spec/group_context.rb +10 -0
- data/lib/view_spec/group_preview.rb +52 -0
- data/lib/view_spec/markdown.rb +20 -0
- data/lib/view_spec/markdown_renderer.rb +13 -0
- data/lib/view_spec/minitest/tests.rb +27 -0
- data/lib/view_spec/note.rb +44 -0
- data/lib/view_spec/param.rb +12 -2
- data/lib/view_spec/param_set.rb +17 -11
- data/lib/view_spec/preview.rb +44 -0
- data/lib/view_spec/registry.rb +7 -23
- data/lib/view_spec/renderable.rb +8 -7
- data/lib/view_spec/scenario.rb +5 -49
- data/lib/view_spec/scenario_collection.rb +4 -0
- data/lib/view_spec/scenario_context.rb +11 -0
- data/lib/view_spec/scenario_preview.rb +30 -0
- data/lib/view_spec/source_file.rb +22 -0
- data/lib/view_spec/spec.rb +3 -13
- data/lib/view_spec/spec_collection.rb +7 -0
- data/lib/view_spec/spec_context.rb +11 -0
- data/lib/view_spec/test_case.rb +10 -0
- data/lib/view_spec/test_helpers.rb +23 -0
- data/lib/view_spec/types/symbol.rb +1 -1
- data/lib/view_spec/utils.rb +42 -1
- data/lib/view_spec/version.rb +1 -1
- data/lib/view_spec.rb +16 -2
- metadata +67 -15
- data/app/views/view_spec/_group.html.erb +0 -20
- data/app/views/view_spec/_scenario.html.erb +0 -17
- data/app/views/view_spec/_spec.html.erb +0 -10
- data/lib/view_spec/dsl/context.rb +0 -54
- data/lib/view_spec/dsl.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0f44c0f0dbadabf2479727d19e5744a33c1d3e029ae7a3ea2d2c2d0a47bfe9f
|
4
|
+
data.tar.gz: 4f78e8fd46c7780e7816358375a0d67ae46e18c7858e67dd1871d7db1349c51c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46dab0defa019b13aa07e9ea04392f810b4673b3a79379bff869d32d3adfeb0a93bd3da59397b2c9c49e8a64eb2d89cfbfa7911cb94a4ae56d7f462460568137
|
7
|
+
data.tar.gz: 2a165392e7a6864738aa841f0926164ad4aa701f0bc9acaad3baaf89d9df33f9b3b995077382a9ca7804ebdf6aedae4a038419937496e430c4fc6995006dabeb
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c)
|
3
|
+
Copyright (c) 2025-present Mark Perkins
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,51 +1,294 @@
|
|
1
|
-
# ViewSpec
|
1
|
+
# ViewSpec
|
2
2
|
|
3
|
-
|
3
|
+
<p><a href="https://rubygems.org/gems/view_spec"><img src="https://img.shields.io/gem/v/view_spec" alt="Gem version"></a>
|
4
|
+
<a href="https://github.com/lookbook/view_spec/actions/workflows/ci.yml"><img src="https://github.com/lookbook/view_spec/actions/workflows/ci.yml/badge.svg" alt="CI status"></a></p>
|
4
5
|
|
5
|
-
|
6
|
+
ViewSpec helps you **develop**, **test** and **document** the view layer of your Rails app using **example-led specifications** written in an **expressive Ruby DSL**. Specs can be created for your app's **components**, **partials**, **helpers**, **views** and more.
|
7
|
+
|
8
|
+
Each spec is made up of one or more **scenarios**, each of which represents a particular state or variant of the the subject of the spec. You can define a **preview**, add contextual **tests** and include **usage information** for each scenario.
|
9
|
+
|
10
|
+
ViewSpec then handles generating **live preview endpoints** and **integrating the tests into your test suite**, and makes it easy to **generate developer documentation** from the specs you create.
|
11
|
+
|
12
|
+
> [!IMPORTANT]
|
13
|
+
> **ViewSpec is at a very early stage of development.** Bugs are very likely and breaking changes may happen without warning.
|
14
|
+
> Please give it a try and share any thoughts, feedback or comments via issues/discussions but be aware that there will likely be significant changes to ViewSpec's functionality and API before a stable `v1.0` release candidate is reached.
|
15
|
+
|
16
|
+
## ViewSpec files and DSL
|
17
|
+
|
18
|
+
ViewSpec spec files are written in Ruby and should be placed in the `test/view_specs` directory. Their filenames should end in `_vspec.rb` and can be organised in whatever subfolder struture is appropriate for your needs.
|
19
|
+
|
20
|
+
ViewSpec specs are written in a bespoke Ruby DSL that should feel somewhat familiar if you have used [RSpec](https://github.com/rspec/rspec) before.
|
21
|
+
|
22
|
+
Here's what an example spec file for a button component might look like:
|
6
23
|
|
7
24
|
```rb
|
8
|
-
# button_vspec.rb
|
25
|
+
# test/view_specs/button_vspec.rb
|
9
26
|
|
10
27
|
ViewSpec.spec ButtonComponent do
|
11
|
-
title "Button (ViewComponent)"
|
12
|
-
|
13
28
|
scenario "default" do
|
29
|
+
notes <<~MD
|
30
|
+
Default button with the `primary` theme.
|
31
|
+
_Appropriate for the majority of use-cases._
|
32
|
+
MD
|
33
|
+
|
14
34
|
preview do
|
15
35
|
render ButtonComponent.new(text: "Button text")
|
16
36
|
end
|
37
|
+
|
38
|
+
tests do
|
39
|
+
test "it renders" do
|
40
|
+
render_preview
|
41
|
+
assert_selector("button")
|
42
|
+
end
|
43
|
+
|
44
|
+
test "it displays the label text" do
|
45
|
+
render_preview
|
46
|
+
assert_selector("button", text: "Button text")
|
47
|
+
end
|
48
|
+
end
|
17
49
|
end
|
18
50
|
|
19
|
-
scenario "
|
51
|
+
scenario "themes" do
|
52
|
+
title "Available Themes"
|
53
|
+
|
54
|
+
notes <<~MD
|
55
|
+
There are three button themes available for use:
|
56
|
+
|
57
|
+
* `:primary` (default)
|
58
|
+
* `:secondary` (use for less important actions)
|
59
|
+
* `:danger` (use for destructive actions)
|
60
|
+
MD
|
61
|
+
|
62
|
+
preview <<~ERB
|
63
|
+
<%= render ButtonComponent.new(text: "Primary", theme: :primary) %>
|
64
|
+
<%= render ButtonComponent.new(text: "Secondary", theme: :secondary) %>
|
65
|
+
<%= render ButtonComponent.new(text: "Danger", theme: :danger) %>
|
66
|
+
ERB
|
67
|
+
|
68
|
+
tests do
|
69
|
+
test "it applies themes to the buttons" do
|
70
|
+
render_preview
|
71
|
+
assert_selector(%(button[data-theme="primary"]))
|
72
|
+
assert_selector(%(button[data-theme="secondary"]))
|
73
|
+
assert_selector(%(button[data-theme="danger"]))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
scenario "playground" do
|
79
|
+
notes <<~MD
|
80
|
+
You can append query parameters to the preview URL to
|
81
|
+
set the button's `text`, `type` and `theme` properties.
|
82
|
+
MD
|
83
|
+
|
20
84
|
param :text, :string, default: "Button text"
|
21
85
|
param :type, :symbol, default: :button
|
86
|
+
param :theme, :symbol, default: :primary
|
22
87
|
|
23
88
|
preview do
|
24
89
|
render ButtonComponent.new(**params)
|
25
90
|
end
|
26
91
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
> [!NOTE]
|
96
|
+
> ViewSpec is completely agnostic to your choice of component framework (e.g. ViewComponent, Phlex, etc), templating language (ERB, HAML, etc) and test suite.
|
97
|
+
> If you can use it with Rails you can use it with ViewSpec!
|
98
|
+
|
99
|
+
## Workflow example
|
36
100
|
|
37
|
-
|
38
|
-
|
39
|
-
|
101
|
+
Below is an outline of what a ViewSpec-based workflow for developing, testing and documenting a new `button` component might look like.
|
102
|
+
|
103
|
+
The button component in this example will be implemented using a standard Rails/ActionView partial, but could equally be created using ViewComponent, Phlex, a view helper or even just vanilla HTML.
|
104
|
+
|
105
|
+
We'll assume that the ViewSpec gem has already been installed and configured in the example app.
|
106
|
+
|
107
|
+
### 1. Create the component
|
108
|
+
|
109
|
+
First we create the basic `button` component partial in the `app/views/components` directory.
|
110
|
+
|
111
|
+
```erb
|
112
|
+
<%# locals: (text:, theme: :primary, type: :button) %>
|
113
|
+
|
114
|
+
<%= button_tag(text, type: type, data: {theme: theme}, class: "button") %>
|
115
|
+
```
|
116
|
+
|
117
|
+
From the file above you can see that there are default values for the `theme` and `type` options. Only the button `text` argument is required to render the partial.
|
118
|
+
|
119
|
+
### 2. Add a spec file
|
120
|
+
|
121
|
+
Now we'll start by creating a bare-bones button spec at `test/view_specs/button_vspec.rb` that includes a single scenario describing the default state of the button.
|
122
|
+
|
123
|
+
```rb
|
124
|
+
# test/view_specs/button_vspec.rb
|
125
|
+
|
126
|
+
ViewSpec.spec "components/button" do
|
127
|
+
title "Basic button"
|
128
|
+
|
129
|
+
scenario "default" do
|
130
|
+
notes <<~MD
|
131
|
+
Default button with `primary` theme.
|
132
|
+
_Appropriate for the majority of use-cases._
|
133
|
+
MD
|
134
|
+
|
135
|
+
preview do
|
136
|
+
render "components/button", text: "Click here"
|
40
137
|
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
### 3. Inspect the preview
|
143
|
+
|
144
|
+
The default button preview defined above will now be available for viewing at [`https://localhost:3000/rails/view_spec/previews/button@default`](https://localhost:3000/rails/view_spec/previews/button@default).
|
145
|
+
|
146
|
+
Previews are rendered in isolation from the rest of your app which makes them perfect for using whilst developing your components/partials/helpers etc.
|
147
|
+
|
148
|
+
> [!NOTE]
|
149
|
+
> By default previews are only available when your app is running in `development` and `test` environments, although this behaviour is configurable if required.
|
150
|
+
|
151
|
+
### 4. Add some tests
|
152
|
+
|
153
|
+
Once a scenario preview has been added you can then add some tests to run against it.
|
154
|
+
|
155
|
+
Tests are defined within the context of the scenario in a `tests do ... end` block.
|
156
|
+
|
157
|
+
For this example we'll add a couple of test to check that the rendered output of this default button component state is as expected.
|
158
|
+
|
159
|
+
```rb
|
160
|
+
# test/view_specs/button_vspec.rb
|
161
|
+
|
162
|
+
ViewSpec.spec "components/button" do
|
163
|
+
# ...
|
164
|
+
|
165
|
+
scenario "default" do
|
166
|
+
# ...
|
41
167
|
|
42
|
-
|
43
|
-
|
168
|
+
preview do
|
169
|
+
render "components/button", text: "Click here"
|
44
170
|
end
|
45
171
|
|
46
|
-
|
47
|
-
|
172
|
+
tests do
|
173
|
+
setup { render_preview }
|
174
|
+
|
175
|
+
test "it renders the button component" do
|
176
|
+
assert_selector("button.button")
|
177
|
+
end
|
178
|
+
|
179
|
+
test "it has the default attributes set" do
|
180
|
+
assert_selector(%(button[type="button"][data-theme="primary"]))
|
181
|
+
end
|
182
|
+
|
183
|
+
test "it displays the label text" do
|
184
|
+
assert_selector("button", text: "Click here")
|
185
|
+
end
|
48
186
|
end
|
49
187
|
end
|
50
188
|
end
|
51
|
-
```
|
189
|
+
```
|
190
|
+
|
191
|
+
### 5. Run the tests
|
192
|
+
|
193
|
+
To run the tests as part of your Minitest-based test suite you will first need to add a ViewSpec test file into your `tests` directory. It should contain the following content:
|
194
|
+
|
195
|
+
```rb
|
196
|
+
# tests/view_specs_test.rb
|
197
|
+
|
198
|
+
require "test_helper"
|
199
|
+
|
200
|
+
module ViewSpecsTest
|
201
|
+
include ViewSpec::Minitest::Tests
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
This only needs to be done once. With with this file in place all the preview tests defined in your spec files will be included everytime your test suite is run using `rails test`.
|
206
|
+
|
207
|
+
If you only wish to run your ViewSpec tests then just run the ViewSpec test file directly:
|
208
|
+
|
209
|
+
```sh
|
210
|
+
rails test test/view_specs_test.rb
|
211
|
+
```
|
212
|
+
|
213
|
+
> [!NOTE]
|
214
|
+
> Only Minitest is currently supported but adapters for RSpec and more are in the works!
|
215
|
+
|
216
|
+
### 6. Add more scenarios
|
217
|
+
|
218
|
+
At this point it often makes sense to add more scenarios to demonstrate/document other states or properties of the subject of the spec.
|
219
|
+
|
220
|
+
A spec can have as many scenarios as needed, each with it's own preview, tests and usage notes.
|
221
|
+
|
222
|
+
> [!TIP]
|
223
|
+
> Scenarios can also be grouped together in a `group do ... end` block if required. See below for more details on scenarios and scenario groups.
|
224
|
+
|
225
|
+
### 7. Generate documentation
|
226
|
+
|
227
|
+
ViewSpec files are designed to act as readable developer-documentation in their own right. However you may also wish to have HTML-formatted documentation for your components/partials/views etc.
|
228
|
+
|
229
|
+
ViewSpec provides some bare-bones default templates to make it easy to generate HTML docs from your specs.
|
230
|
+
|
231
|
+
For simplicity the example below just shows how you could generate docs specifically for the single button spec defined above (although in reality you would probably want to make this dynamic so you wouldn't need to manually add a separate action for each spec file).
|
232
|
+
|
233
|
+
```rb
|
234
|
+
class DocsController < ApplicationController
|
235
|
+
def button
|
236
|
+
@spec = ViewSpec.specs.find(ButtonComponent)
|
237
|
+
@params = request.query_parameters
|
238
|
+
end
|
239
|
+
end
|
240
|
+
```
|
241
|
+
|
242
|
+
```erb
|
243
|
+
# views/docs/button.html.erb
|
244
|
+
|
245
|
+
<%= render @spec, params: @params %>
|
246
|
+
```
|
247
|
+
|
248
|
+
The example above will render documentation for the button spec using [the default templates](./app/views/view_specs/).
|
249
|
+
|
250
|
+
Styling the docs with CSS is up to you, no default stylesheet is provided. With some basic styles applied the default templates look like this:
|
251
|
+
|
252
|
+
<img src="./.github/assets/button_view_spec_docs.png">
|
253
|
+
|
254
|
+
You can override one or all of the default templates by copying them into a `view_specs` folder in your `app/views` directory, and then customising them as needed.
|
255
|
+
|
256
|
+
## Usage
|
257
|
+
|
258
|
+
ViewSpec has the following requirements:
|
259
|
+
|
260
|
+
* `Rails 7.0+`
|
261
|
+
* `Ruby 3.1+`
|
262
|
+
|
263
|
+
### Installation
|
264
|
+
|
265
|
+
Add the following to your Rails app Gemfile:
|
266
|
+
|
267
|
+
```rb
|
268
|
+
gem "view_spec"
|
269
|
+
```
|
270
|
+
|
271
|
+
And then run `bundle install`. You are good to go!
|
272
|
+
|
273
|
+
### Configuration
|
274
|
+
|
275
|
+
_Docs coming soon_
|
276
|
+
|
277
|
+
### ViewSpec DSL
|
278
|
+
|
279
|
+
_Docs coming soon_
|
280
|
+
|
281
|
+
## About ViewSpec (& Lookbook)
|
282
|
+
|
283
|
+
ViewSpec is being developed as a standalone replacement for [Lookbook's](https://lookbook.build/) underlying [preview system](https://lookbook.build/guide/previews).
|
284
|
+
|
285
|
+
It shares a lot of concepts with the existing Lookbook preview class format (which was based on ViewComponent's preview system - itself in turn inspired by Rails' ActionMailer previews). However using a pure Ruby DSL in place of YARD-style comment tags results in much better performance and has many other benefits (including 'native' syntax error handling and more).
|
286
|
+
|
287
|
+
Additionally, having a standalone file format and preview system which is not tied to any particular UI tool (such as Lookbook) or component system (such as ViewComponent) hopefully means that ViewSpec can be adopted by teams that don't want those dependencies but do still want a way to efficiently develop and document the frontend of their Rails app.
|
288
|
+
|
289
|
+
**The plan is for the next major version of Lookbook to built upon the ViewSpec file format**. It will provide a user interface for browsing and inspecting your ViewSpec previews in much the same way it does at the moment for the current (ViewComponent-style) preview class files.
|
290
|
+
|
291
|
+
_(Note that the current `v2.x` branch of Lookbook is **not** compatible with ViewSpec and there is no intention to add support for it in the future.)_
|
292
|
+
|
293
|
+
> [!IMPORTANT]
|
294
|
+
> Projects can (and always will be able to) use ViewSpec to help preview, test and document their view layer **without any requirement** to use Lookbook.
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ViewSpecs
|
2
|
+
module PreviewActions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def index
|
6
|
+
@view_specs = ViewSpec.specs
|
7
|
+
|
8
|
+
layout = self.class._layout || ViewSpec.config.preview_layout
|
9
|
+
options = {}
|
10
|
+
options[:layout] = layout if layout
|
11
|
+
|
12
|
+
render "view_specs/previews/index", **options
|
13
|
+
end
|
14
|
+
|
15
|
+
def preview
|
16
|
+
spec = ViewSpec.specs.find_where(lookup_path: params[:spec])
|
17
|
+
raise ActionController::RoutingError, "Spec not found" unless spec
|
18
|
+
|
19
|
+
entry = spec.entries.find_where(short_identifier: params[:entry])
|
20
|
+
raise ActionController::RoutingError, "`#{params[:entry]}` not found for spec `#{spec.subject}`" unless entry
|
21
|
+
|
22
|
+
preview = entry.preview(params: view_spec_preview_params)
|
23
|
+
|
24
|
+
options = {}
|
25
|
+
layout = entry.determine_layout(self.class._layout) || ViewSpec.config.preview_layout
|
26
|
+
(options[:layout] = layout) if layout
|
27
|
+
|
28
|
+
render preview, **options
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def view_spec_preview_params
|
34
|
+
request.query_parameters.fetch(:params, {})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
<%# locals: (group:, params: {}, details: true) -%>
|
2
|
+
|
3
|
+
<%
|
4
|
+
details = (details == true) ? [ :notes, :preview, :source, :html] : Array.wrap(details)
|
5
|
+
previewable_scenarios = group.scenarios.filter(&:preview?)
|
6
|
+
request_params = request.query_parameters.fetch("params", {})
|
7
|
+
%>
|
8
|
+
|
9
|
+
<section class="vs-entry vs-entry--group">
|
10
|
+
<header class="vs-entry-header">
|
11
|
+
<h2 class="vs-entry-title"><%= group.title %></h2>
|
12
|
+
</header>
|
13
|
+
<div class="vs-entry-content">
|
14
|
+
<% if details.include?(:notes) && group.notes? %>
|
15
|
+
<article class="vs-entry-notes">
|
16
|
+
<%= group.notes %>
|
17
|
+
</article>
|
18
|
+
<% end %>
|
19
|
+
<% if previewable_scenarios.any? %>
|
20
|
+
<% preview = group.preview(params:) %>
|
21
|
+
<% if details.include?(:preview) %>
|
22
|
+
<details class="vs-entry-details vs-entry-details--preview" open>
|
23
|
+
<summary>
|
24
|
+
<h4 class="vs-entry-subtitle">Preview</h4>
|
25
|
+
</summary>
|
26
|
+
<p class="vs-entry-preview-link">
|
27
|
+
<%= link_to "View in new window", preview.to_path(params: {params: request_params}), target: "_blank" %>
|
28
|
+
</p>
|
29
|
+
<div class="vs-entry-details-display vs-preview">
|
30
|
+
<%= preview %>
|
31
|
+
</div>
|
32
|
+
</details>
|
33
|
+
<% end %>
|
34
|
+
<% if details.include?(:source) %>
|
35
|
+
<details class="vs-entry-details vs-entry-details--source" open>
|
36
|
+
<summary>
|
37
|
+
<h4 class="vs-entry-subtitle">Source</h4>
|
38
|
+
</summary>
|
39
|
+
<code class="vs-code-block" data-lang="<%= preview.lang %>">
|
40
|
+
<pre><%= preview.source %></pre>
|
41
|
+
</code>
|
42
|
+
</details>
|
43
|
+
<% end %>
|
44
|
+
<% if details.include?(:html) %>
|
45
|
+
<details class="vs-entry-details vs-entry-details--html">
|
46
|
+
<summary>
|
47
|
+
<h4 class="vs-entry-subtitle">Output</h4>
|
48
|
+
</summary>
|
49
|
+
<code class="vs-code-block" data-lang="html">
|
50
|
+
<pre><%= preview.raw %></pre>
|
51
|
+
</code>
|
52
|
+
</details>
|
53
|
+
<% end %>
|
54
|
+
<% end %>
|
55
|
+
</div>
|
56
|
+
</section>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<%# locals: (scenario:, params: {}, details: true) -%>
|
2
|
+
|
3
|
+
<%
|
4
|
+
details = (details == true) ? [ :notes, :preview, :source, :html] : Array.wrap(details)
|
5
|
+
request_params = request.query_parameters.fetch("params", {})
|
6
|
+
%>
|
7
|
+
|
8
|
+
<section class="vs-entry vs-entry--scenario">
|
9
|
+
<header class="vs-entry-header">
|
10
|
+
<h3 class="vs-entry-title"><%= scenario.title %></h3>
|
11
|
+
</header>
|
12
|
+
<div class="vs-entry-content">
|
13
|
+
<% if details.include?(:notes) && scenario.notes? %>
|
14
|
+
<article class="vs-entry-notes">
|
15
|
+
<%= scenario.notes %>
|
16
|
+
</article>
|
17
|
+
<% end %>
|
18
|
+
<% if scenario.preview? %>
|
19
|
+
<% preview = scenario.preview(params:) %>
|
20
|
+
<% if details.include?(:preview) %>
|
21
|
+
<details class="vs-entry-details vs-entry-details--preview" open>
|
22
|
+
<summary>
|
23
|
+
<h4 class="vs-entry-subtitle">Preview</h4>
|
24
|
+
</summary>
|
25
|
+
<p class="vs-entry-preview-link">
|
26
|
+
<%= link_to "View in new window", preview.to_path(params: {params: request_params}), target: "_blank" %>
|
27
|
+
</p>
|
28
|
+
<div class="vs-preview">
|
29
|
+
<%= preview %>
|
30
|
+
</div>
|
31
|
+
</details>
|
32
|
+
<% end %>
|
33
|
+
<% if details.include?(:source) %>
|
34
|
+
<details class="vs-entry-details vs-entry-details--source" open>
|
35
|
+
<summary>
|
36
|
+
<h4 class="vs-entry-subtitle">Source</h4>
|
37
|
+
</summary>
|
38
|
+
<code class="vs-code-block" data-lang="<%= preview.lang %>">
|
39
|
+
<pre><%= preview.source %></pre>
|
40
|
+
</code>
|
41
|
+
</details>
|
42
|
+
<% end %>
|
43
|
+
<% if details.include?(:html) %>
|
44
|
+
<details class="vs-entry-details vs-entry-details--html">
|
45
|
+
<summary>
|
46
|
+
<h4 class="vs-entry-subtitle">HTML output</h4>
|
47
|
+
</summary>
|
48
|
+
<code class="vs-code-block" data-lang="html">
|
49
|
+
<pre><%= preview.raw %></pre>
|
50
|
+
</code>
|
51
|
+
</details>
|
52
|
+
<% end %>
|
53
|
+
<% end %>
|
54
|
+
</div>
|
55
|
+
</section>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<%# locals: (spec:, params: {}, header: true, details: true) -%>
|
2
|
+
|
3
|
+
<article class="vs-spec" data-view-spec data-view-spec-subject="<%= spec.subject %>">
|
4
|
+
<% if header %>
|
5
|
+
<header class="vs-spec-header">
|
6
|
+
<h2 class="vs-spec-title"><%= spec.title %></h2>
|
7
|
+
</header>
|
8
|
+
<% end %>
|
9
|
+
<div class="vs-spec-content">
|
10
|
+
<div class="vs-spec-entries">
|
11
|
+
<% spec.entries.each do |entry| %>
|
12
|
+
<div class="vs-spec-entry">
|
13
|
+
<%= render entry, params:, details: %>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
</article>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%# locals: {group:, notes: nil} %>
|
2
|
+
<% scenarios_with_notes = group.scenarios.filter { _1.notes? } %>
|
3
|
+
<%= notes if notes.present? %>
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
<% scenarios_with_notes.each do |scenario| %>
|
8
|
+
<div style="all: revert; padding-left: 0.5rem">
|
9
|
+
<h4 style="all: revert; margin: 0 0 0.5rem -0.5rem;"><%= scenario.title %></h4>
|
10
|
+
<%= scenario.notes %>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<%# locals: {group:, params: {}} %>
|
2
|
+
|
3
|
+
<% group.scenarios.each do |scenario| %>
|
4
|
+
<% preview = scenario.preview(params:) %>
|
5
|
+
<div style="display: block;<%= " margin-bottom: 1.5rem;" unless scenario == group.scenarios.last %>">
|
6
|
+
<h6 style="all: revert; display: flex; align-items: center; font-family: ui-monospace, monospace; font-weight: normal; font-size: 0.9rem; margin-top: 0; margin-bottom: 0.8rem; color: currentColor">
|
7
|
+
<span style="all: revert; margin-right: 0.3rem; color: currentColor"><%= scenario.title %></span>
|
8
|
+
<a style="all: revert; display: flex; align-items: center; text-decoration: underline; cursor: pointer; opacity: 0.7;" href="<%= preview.to_path %>" target="_blank" title="Open in new window">
|
9
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="unset: all; display: inline-block;">
|
10
|
+
<path d="M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"/><path d="m21 3-9 9"/>
|
11
|
+
<path d="M15 3h6v6"/>
|
12
|
+
</svg>
|
13
|
+
</a>
|
14
|
+
</h6>
|
15
|
+
<div>
|
16
|
+
<%= preview %>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
<% end %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<% if @view_specs.any? %>
|
2
|
+
<ul id="view-spec-previews">
|
3
|
+
<% @view_specs.each do |view_spec| %>
|
4
|
+
<li>
|
5
|
+
<h2><%= view_spec.title %></h2>
|
6
|
+
<% if view_spec.previews? %>
|
7
|
+
<ul>
|
8
|
+
<% view_spec.entries.each do |entry| %>
|
9
|
+
<% if entry.preview? %>
|
10
|
+
<li>
|
11
|
+
<%= link_to entry.title, entry.preview.to_path %>
|
12
|
+
</li>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
</ul>
|
16
|
+
<% else %>
|
17
|
+
<p><em>No previews added.</em></p>
|
18
|
+
<% end %>
|
19
|
+
</li>
|
20
|
+
<% end%>
|
21
|
+
</ul>
|
22
|
+
<% end %>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ViewSpec
|
2
|
+
module Collection
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
included do
|
7
|
+
attr_reader :items
|
8
|
+
delegate_missing_to :items
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(items = [])
|
12
|
+
@items = items.is_a?(Collection) ? items.to_a : items
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_where(conditions)
|
16
|
+
where(conditions).first
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_where!(conditions)
|
20
|
+
result = find_where(conditions)
|
21
|
+
result.nil? ? raise("No matching item found for conditions #{conditions}") : result
|
22
|
+
end
|
23
|
+
|
24
|
+
def where(conditions)
|
25
|
+
conditions.reduce(items.dup) do |collection, (key, value)|
|
26
|
+
collection.filter! { _1.respond_to?(key) && (_1.public_send(key) == value) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def <<(item)
|
31
|
+
items.push(item)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def +(other)
|
36
|
+
self.class.new(items + other.items)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_a
|
40
|
+
items
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|