snapshot_ui 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed5906abe86dd34b16173cdbff8239e7f649ab4ace8adceabf250c90165b1879
4
- data.tar.gz: fce78cca8afc9baffd515257e1f5e7a564e27d803b6c2e3a84341441254d7ead
3
+ metadata.gz: 5d083a6167cbd33d814565a34c6da82c32f920bf1fef6faa8171e1cf4bae1f86
4
+ data.tar.gz: 15f7d8346e5f192e4b8901760b64a1d91362507bb7adf91cbaed37e30a257613
5
5
  SHA512:
6
- metadata.gz: 2c64f1972c0f1261fe57e09e8aad3e9e9ba174184dbc9ff82ed988012e27a8d2340818629189d90a41d3ea7f2d11725ff9ec546500b32a1c0adddf6d2ec083a7
7
- data.tar.gz: 791edbd08ad2eaffed422b850063dc8b3c5914a2214f8914f2079607c03a6c0b3cfa8b6608618e4d54750953886b633a9a7d26f3c975c8e06f5ced609f533b1f
6
+ metadata.gz: 07c26236a62e772945eb5165e2552d2a278493acb7b65d4429f445e98b2401b5ee39acfe842b2a5c9787220b5444ac3f0a1ecab1f7e8cb0a7f87e2f01726b2b2
7
+ data.tar.gz: 5fa30fbda712efff8c596acca55ec3811fee661a6684fa7d34aee7a30b2032255e14930c8288f90e5e5fe15d82424cef66848a4860d560241d9631e57837b94a
@@ -6,18 +6,19 @@ require "async/websocket/client"
6
6
  require "async/websocket/adapters/rack"
7
7
  require "listen"
8
8
  require "set"
9
+ require "uri"
9
10
 
10
- SNAPSHOT_DIRECTORY = ENV.fetch("SNAPSHOT_DIRECTORY")
11
- WEBSOCKET_ENDPOINT = "http://localhost:7070/cable"
11
+ load ENV.fetch("SNAPSHOT_UI_INITIALIZER_FILE")
12
12
 
13
13
  REFRESH_MESSAGE = {identifier: "{\"channel\":\"RefreshChannel\"}", message: "refresh"}.to_json
14
14
  CONFIRM_SUBSCRIPTION_MESSAGE = {identifier: "{\"channel\":\"RefreshChannel\"}", type: "confirm_subscription"}.to_json
15
- PING_MESSAGE = {type: "ping", message: Time.now.to_i.to_s}.to_json
16
15
 
17
16
  @connections = Set.new
18
17
 
18
+ live_websocket_uri = URI.parse(SnapshotUI.configuration.live_websocket_url)
19
+
19
20
  run lambda { |env|
20
- if env["REQUEST_PATH"] == "/cable"
21
+ if env["REQUEST_PATH"] == live_websocket_uri.path
21
22
  Async::WebSocket::Adapters::Rack.open(env, protocols: ["actioncable-v1-json"]) do |connection|
22
23
  @connections << connection
23
24
 
@@ -27,7 +28,7 @@ run lambda { |env|
27
28
 
28
29
  Async do |ping_task|
29
30
  loop do
30
- connection.write(PING_MESSAGE)
31
+ connection.write({type: "ping", message: Time.now.to_i.to_s}.to_json)
31
32
  connection.flush
32
33
  sleep(2)
33
34
  end
@@ -50,7 +51,7 @@ run lambda { |env|
50
51
  }
51
52
 
52
53
  Async do |client_task|
53
- endpoint = Async::HTTP::Endpoint.parse(WEBSOCKET_ENDPOINT)
54
+ endpoint = Async::HTTP::Endpoint.parse(SnapshotUI.configuration.live_websocket_url)
54
55
 
55
56
  Async::WebSocket::Client.connect(endpoint) do |connection|
56
57
  Async do |listener_task|
@@ -68,15 +69,16 @@ Async do |client_task|
68
69
  end
69
70
 
70
71
  def detect_snapshots_update(task)
71
- Pathname.new(SNAPSHOT_DIRECTORY).mkpath
72
- listener = Listen.to(SNAPSHOT_DIRECTORY) { |_modified, _added, _removed| broadcast_update }
72
+ Pathname.new(SnapshotUI.configuration.storage_directory).mkpath
73
+ listener = Listen.to(SnapshotUI.configuration.storage_directory) { |_modified, _added, _removed| broadcast_update }
73
74
 
74
75
  task.async do
75
76
  listener.start
76
77
  task.sleep
77
78
  end
78
79
 
79
- Console.info("Watching for snapshots updates in #{SNAPSHOT_DIRECTORY}...")
80
+ Console.info("Watching for snapshots updates in #{SnapshotUI.configuration.storage_directory}")
81
+ Console.info("Review snapshots on #{SnapshotUI.configuration.web_url}")
80
82
  end
81
83
 
82
84
  def broadcast_update
@@ -1,22 +1,25 @@
1
1
  require "bundler/setup"
2
2
  require "thor"
3
3
  require "pathname"
4
+ require "uri"
4
5
 
5
6
  module SnapshotUI
6
7
  class CLI < Thor
7
- WEBSOCKET_HOST = "localhost:7070"
8
-
9
- desc "watch SNAPSHOT_DIRECTORY", "Watches for snapshot changes in SNAPSHOT_DIRECTORY and broadcasts them on ws://#{WEBSOCKET_HOST}/cable."
10
- def watch(snapshot_directory)
11
- unless File.exist?(snapshot_directory)
12
- puts "The provided directory `#{snapshot_directory}` doesn't exist. Please double check the path."
8
+ desc "live SNAPSHOT_UI_INITIALIZER_FILE", "Run the command to enable live refreshing of UI snapshots."
9
+ def live(initializer_file)
10
+ unless File.exist?(initializer_file)
11
+ puts "The provided initializer file `#{initializer_file}` doesn't exist. Please double check the path."
13
12
  exit 1
14
13
  end
15
14
 
16
- websocket_host = "http://#{WEBSOCKET_HOST}"
15
+ load initializer_file
16
+
17
+ websocket_host_uri = URI.parse(SnapshotUI.configuration.live_websocket_url)
18
+ websocket_host_uri.path = ""
19
+
17
20
  config_path = Pathname.new(__dir__).join("cli", "watcher.ru").cleanpath.to_s
18
21
 
19
- exec "SNAPSHOT_DIRECTORY=#{snapshot_directory} bundle exec falcon serve --bind #{websocket_host} --count 1 --config #{config_path}"
22
+ exec "SNAPSHOT_UI_INITIALIZER_FILE=#{initializer_file} bundle exec falcon serve --bind #{websocket_host_uri} --count 1 --config #{config_path}"
20
23
  end
21
24
 
22
25
  def self.exit_on_failure?
@@ -5,12 +5,13 @@ require_relative "colorize"
5
5
  module SnapshotUI
6
6
  class Configuration
7
7
  attr_writer :storage_directory, :project_root_directory
8
- attr_accessor :web_url
8
+ attr_accessor :web_url, :live_websocket_url
9
9
 
10
- def initialize(project_root_directory:, storage_directory:, web_url:)
10
+ def initialize(project_root_directory:, storage_directory:, web_url:, live_websocket_url:)
11
11
  @project_root_directory = project_root_directory
12
12
  @storage_directory = storage_directory
13
13
  @web_url = web_url
14
+ @live_websocket_url = live_websocket_url
14
15
  end
15
16
 
16
17
  def storage_directory
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SnapshotUI
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -119,6 +119,33 @@ module SnapshotUI
119
119
  def refresh_controller
120
120
  'data-controller="refresh" data-action="refresh-connected@window->refresh#connected refresh-disconnected@window->refresh#disconnected turbo:before-render@window->refresh#display_status"'
121
121
  end
122
+
123
+ def snapshot_title(snapshot)
124
+ title = snapshot.context.name.sub("test_", "").gsub(/^\d{4}\s*/, "").tr("_", " ")
125
+ suffix =
126
+ if snapshot.context.take_snapshot_index > 0
127
+ " (##{snapshot.context.take_snapshot_index + 1} in the same test)"
128
+ end
129
+
130
+ "#{title}#{suffix}"
131
+ end
132
+
133
+ def test_group_title(test_group)
134
+ parts = test_group.split("::")
135
+ last_part = "<span class='last'>#{parts.last}</span>"
136
+ all = parts[0..-2] << last_part
137
+
138
+ all.join(" <span class='divider'>/</span> ")
139
+ end
140
+
141
+ def copy_icon_svg
142
+ <<~HTML
143
+ <svg class="copy icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
144
+ <!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
145
+ <path fill="currentColor" d="M384 336l-192 0c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l140.1 0L400 115.9 400 320c0 8.8-7.2 16-16 16zM192 384l192 0c35.3 0 64-28.7 64-64l0-204.1c0-12.7-5.1-24.9-14.1-33.9L366.1 14.1c-9-9-21.2-14.1-33.9-14.1L192 0c-35.3 0-64 28.7-64 64l0 256c0 35.3 28.7 64 64 64zM64 128c-35.3 0-64 28.7-64 64L0 448c0 35.3 28.7 64 64 64l192 0c35.3 0 64-28.7 64-64l0-32-48 0 0 32c0 8.8-7.2 16-16 16L64 464c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l32 0 0-48-32 0z"/>
146
+ </svg>
147
+ HTML
148
+ end
122
149
  end
123
150
  end
124
151
  end
@@ -3,6 +3,8 @@ import "@hotwired/turbo"
3
3
  import "channels"
4
4
 
5
5
  import RefreshController from "controllers/refresh_controller"
6
+ import SourceLocationController from "controllers/source_location_controller"
6
7
 
7
8
  window.Stimulus = Application.start()
8
9
  Stimulus.register("refresh", RefreshController)
10
+ Stimulus.register("source-location", SourceLocationController)
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ copy(event) {
5
+ event.preventDefault()
6
+
7
+ let currentTarget = event.currentTarget
8
+
9
+ navigator.clipboard.writeText(currentTarget.dataset.sourceLocation)
10
+
11
+ currentTarget.classList.toggle('animate');
12
+ setTimeout(()=> {
13
+ currentTarget.classList.toggle('animate')
14
+ },300)
15
+
16
+ }
17
+ }
@@ -28,6 +28,15 @@ body#snapshots_index h1 {
28
28
  body#snapshots_index h2 {
29
29
  margin: 30px 0 0;
30
30
  font-size: 24px;
31
+ color: #aaa;
32
+ }
33
+
34
+ body#snapshots_index h2 .last {
35
+ color: #666666;
36
+ }
37
+
38
+ body#snapshots_index h2 .divider {
39
+ color: #ccc;
31
40
  }
32
41
 
33
42
  body#snapshots_index p {
@@ -44,8 +53,14 @@ body#snapshots_index h1, h2, h3, h4, h5, h6 {
44
53
  color: #666666;
45
54
  }
46
55
 
47
- body#snapshots_index a { color: #000; text-decoration: none; }
48
- body#snapshots_index a:hover { text-decoration: underline; }
56
+ body#snapshots_index a {
57
+ color: #000;
58
+ text-decoration: none;
59
+ }
60
+
61
+ body#snapshots_index a:hover {
62
+ text-decoration: underline;
63
+ }
49
64
 
50
65
  body#snapshots_index code {
51
66
  background: rgba(175, 184, 193, 0.2);
@@ -101,6 +116,67 @@ body#not_found p {
101
116
  color: #666666;
102
117
  }
103
118
 
119
+ .copy_source_location {
120
+ all: unset;
121
+ cursor: pointer;
122
+ position: relative;
123
+ }
124
+
125
+ .copy_source_location .icon {
126
+ width: 0.9em;
127
+ height: 0.9em;
128
+ color: #aaa;
129
+ }
130
+
131
+ .copy_source_location:hover .icon {
132
+ color: #000;
133
+ }
134
+
135
+ .copy_source_location.animate .icon {
136
+ animation: enlarge-and-back-to-normal 0.3s;
137
+ }
138
+
139
+ @keyframes enlarge-and-back-to-normal {
140
+ 0%,
141
+ 100% {
142
+ transform: scale(1, 1);
143
+ }
144
+ 50% {
145
+ transform: scale(1.3, 1.3);
146
+ }
147
+ }
148
+
149
+ .copy_source_location:before {
150
+ content: attr(data-source-location);
151
+ position:absolute;
152
+ font-size: 90%;
153
+ top:-95%;
154
+ transform:translateX(-50%);
155
+ left:100%;
156
+ margin-top:-15px;
157
+ padding:3px 15px 1px;
158
+ border-radius:10px;
159
+ background:#eee;
160
+ color: #666;
161
+ text-align:center;
162
+ display:none;
163
+ }
164
+
165
+ .copy_source_location:after {
166
+ content: "";
167
+ position:absolute;
168
+ top:-11px;
169
+ transform:translateX(-50%);
170
+ margin-left:10px;
171
+ border:10px solid #eee;
172
+ border-color: #eee transparent transparent transparent;
173
+ display:none;
174
+ }
175
+
176
+ .copy_source_location:hover:before, .copy_source_location:hover:after {
177
+ display:block;
178
+ }
179
+
104
180
  /* Hotwired Turbo related */
105
181
  .turbo-progress-bar {
106
182
  height: 2px;
@@ -7,7 +7,7 @@
7
7
  <meta name="turbo-refresh-method" content="morph">
8
8
  <meta name="turbo-refresh-scroll" content="preserve">
9
9
  <link rel="stylesheet" href="<%= stylesheet_path("application.css") %>">
10
- <meta name="action-cable-url" content="ws://127.0.0.1:7070/cable" />
10
+ <meta name="action-cable-url" content="<%= SnapshotUI.configuration.live_websocket_url %>" />
11
11
  <script type="importmap" data-turbo-track="reload">
12
12
  {
13
13
  "imports": {
@@ -16,6 +16,7 @@
16
16
  "@hotwired/turbo": "https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.4/+esm",
17
17
  "@rails/actioncable": "https://cdn.jsdelivr.net/npm/@rails/actioncable@7.1.3-4/+esm",
18
18
  "controllers/refresh_controller": "<%= javascript_path("controllers/refresh_controller.js") %>",
19
+ "controllers/source_location_controller": "<%= javascript_path("controllers/source_location_controller.js") %>",
19
20
  "channels/consumer": "<%= javascript_path("channels/consumer.js") %>",
20
21
  "channels": "<%= javascript_path("channels/index.js") %>",
21
22
  "channels/refresh_channel": "<%= javascript_path("channels/refresh_channel.js") %>"
@@ -44,15 +44,21 @@ bundle exec snapshot_ui watch SNAPSHOT_DIRECTORY</pre>
44
44
  <% end %>
45
45
 
46
46
  <% @grouped_by_test_class.each do |test_group, snapshots| %>
47
- <h2><%= test_group %></h2>
47
+
48
+ <h2><%= test_group_title(test_group) %></h2>
48
49
 
49
50
  <ul>
50
51
  <% snapshots.each do |snapshot| %>
51
52
  <li>
52
- <a href="<%= snapshot_path(snapshot.slug) %>">
53
- <%= snapshot.context.name %>
54
- <%= if snapshot.context.take_snapshot_index > 0 then "(##{(snapshot.context.take_snapshot_index + 1)} in the same test)" end %>
55
- </a>
53
+ <a href="<%= snapshot_path(snapshot.slug) %>"><%= snapshot_title(snapshot) %></a>
54
+ <button
55
+ class="copy_source_location"
56
+ data-controller="source-location"
57
+ data-action="click->source-location#copy"
58
+ data-source-location="<%= snapshot.context.source_location.join(":") %>"
59
+ >
60
+ <%= copy_icon_svg %>
61
+ </button>
56
62
  </li>
57
63
  <% end %>
58
64
  </ul>
data/lib/snapshot_ui.rb CHANGED
@@ -7,7 +7,8 @@ module SnapshotUI
7
7
  DEFAULT_CONFIGURATION = {
8
8
  project_root_directory: nil,
9
9
  storage_directory: nil,
10
- web_url: "http://localhost:3000/ui/snapshots"
10
+ web_url: "http://localhost:3000/ui/snapshots",
11
+ live_websocket_url: "http://localhost:49152/live"
11
12
  }.freeze
12
13
 
13
14
  def self.configure
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snapshot_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomaz Zlender
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-26 00:00:00.000000000 Z
11
+ date: 2024-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -110,6 +110,7 @@ files:
110
110
  - lib/snapshot_ui/web/assets/javascripts/channels/index.js
111
111
  - lib/snapshot_ui/web/assets/javascripts/channels/refresh_channel.js
112
112
  - lib/snapshot_ui/web/assets/javascripts/controllers/refresh_controller.js
113
+ - lib/snapshot_ui/web/assets/javascripts/controllers/source_location_controller.js
113
114
  - lib/snapshot_ui/web/assets/stylesheets/application.css
114
115
  - lib/snapshot_ui/web/views/layout.html.erb
115
116
  - lib/snapshot_ui/web/views/snapshots/index.html.erb