bard-attachment_field 0.2.0 → 0.2.2
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/app/assets/javascripts/input-attachment.js +69 -22
- data/input-attachment/bun.lockb +0 -0
- data/input-attachment/package.json +1 -0
- data/input-attachment/src/components/input-attachment/form-controller.tsx +1 -0
- data/input-attachment/src/components/input-attachment/input-attachment.tsx +7 -6
- data/lib/bard/attachment_field/cucumber.rb +79 -27
- data/lib/bard/attachment_field/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 32a9e349c2ce7715b8a5732e730bbd96efb6c50f542b0f39fd470a1b39b2952e
|
|
4
|
+
data.tar.gz: 1da2861ccab4b81ffc92f0dc6b933c9266b07ba2b96747469d3030de9f44f9f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26808a1afc2c0156726a2fb3443a600efb194bdf87ad4d9a284896e0a53ed24ba3cae9170875fa7bb27cc933de8eafa71fc5116669531ab3431329c59ec6c323
|
|
7
|
+
data.tar.gz: 0f48786c5c1eea01995a6e8d04e41f3cb24c5866456b99068d1a4cf6bbedf41e5879df2d02b4f28b02bb5b939f454a9230e2f7bf7e0a49c5a09ef618b200a2cd
|
|
@@ -5070,6 +5070,9 @@ var FetchResponse = class {
|
|
|
5070
5070
|
get isTurboStream() {
|
|
5071
5071
|
return this.contentType.match(/^text\/vnd\.turbo-stream\.html/);
|
|
5072
5072
|
}
|
|
5073
|
+
get isScript() {
|
|
5074
|
+
return this.contentType.match(/\b(?:java|ecma)script\b/);
|
|
5075
|
+
}
|
|
5073
5076
|
async renderTurboStream() {
|
|
5074
5077
|
if (this.isTurboStream) {
|
|
5075
5078
|
if (window.Turbo) {
|
|
@@ -5081,6 +5084,22 @@ var FetchResponse = class {
|
|
|
5081
5084
|
return Promise.reject(new Error(`Expected a Turbo Stream response but got "${this.contentType}" instead`));
|
|
5082
5085
|
}
|
|
5083
5086
|
}
|
|
5087
|
+
async activeScript() {
|
|
5088
|
+
if (this.isScript) {
|
|
5089
|
+
const script = document.createElement("script");
|
|
5090
|
+
const metaTag = document.querySelector("meta[name=csp-nonce]");
|
|
5091
|
+
if (metaTag) {
|
|
5092
|
+
const nonce = metaTag.nonce === "" ? metaTag.content : metaTag.nonce;
|
|
5093
|
+
if (nonce) {
|
|
5094
|
+
script.setAttribute("nonce", nonce);
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
script.innerHTML = await this.text;
|
|
5098
|
+
document.body.appendChild(script);
|
|
5099
|
+
} else {
|
|
5100
|
+
return Promise.reject(new Error(`Expected a Script response but got "${this.contentType}" instead`));
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5084
5103
|
};
|
|
5085
5104
|
var RequestInterceptor = class {
|
|
5086
5105
|
static register(interceptor) {
|
|
@@ -5126,7 +5145,7 @@ function stringEntriesFromFormData(formData) {
|
|
|
5126
5145
|
function mergeEntries(searchParams, entries) {
|
|
5127
5146
|
for (const [name, value] of entries) {
|
|
5128
5147
|
if (value instanceof window.File) continue;
|
|
5129
|
-
if (searchParams.has(name)) {
|
|
5148
|
+
if (searchParams.has(name) && !name.includes("[]")) {
|
|
5130
5149
|
searchParams.delete(name);
|
|
5131
5150
|
searchParams.set(name, value);
|
|
5132
5151
|
} else {
|
|
@@ -5149,11 +5168,16 @@ var FetchRequest = class {
|
|
|
5149
5168
|
} catch (error) {
|
|
5150
5169
|
console.error(error);
|
|
5151
5170
|
}
|
|
5152
|
-
const
|
|
5171
|
+
const fetch = window.Turbo ? window.Turbo.fetch : window.fetch;
|
|
5172
|
+
const response = new FetchResponse(await fetch(this.url, this.fetchOptions));
|
|
5153
5173
|
if (response.unauthenticated && response.authenticationURL) {
|
|
5154
5174
|
return Promise.reject(window.location.href = response.authenticationURL);
|
|
5155
5175
|
}
|
|
5156
|
-
if (response.
|
|
5176
|
+
if (response.isScript) {
|
|
5177
|
+
await response.activeScript();
|
|
5178
|
+
}
|
|
5179
|
+
const responseStatusIsTurboStreamable = response.ok || response.unprocessableEntity;
|
|
5180
|
+
if (responseStatusIsTurboStreamable && response.isTurboStream) {
|
|
5157
5181
|
await response.renderTurboStream();
|
|
5158
5182
|
}
|
|
5159
5183
|
return response;
|
|
@@ -5163,27 +5187,38 @@ var FetchRequest = class {
|
|
|
5163
5187
|
headers[key] = value;
|
|
5164
5188
|
this.options.headers = headers;
|
|
5165
5189
|
}
|
|
5190
|
+
sameHostname() {
|
|
5191
|
+
if (!this.originalUrl.startsWith("http:") && !this.originalUrl.startsWith("https:")) {
|
|
5192
|
+
return true;
|
|
5193
|
+
}
|
|
5194
|
+
try {
|
|
5195
|
+
return new URL(this.originalUrl).hostname === window.location.hostname;
|
|
5196
|
+
} catch (_) {
|
|
5197
|
+
return true;
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5166
5200
|
get fetchOptions() {
|
|
5167
5201
|
return {
|
|
5168
5202
|
method: this.method.toUpperCase(),
|
|
5169
5203
|
headers: this.headers,
|
|
5170
5204
|
body: this.formattedBody,
|
|
5171
5205
|
signal: this.signal,
|
|
5172
|
-
credentials:
|
|
5173
|
-
redirect: this.redirect
|
|
5206
|
+
credentials: this.credentials,
|
|
5207
|
+
redirect: this.redirect,
|
|
5208
|
+
keepalive: this.keepalive
|
|
5174
5209
|
};
|
|
5175
5210
|
}
|
|
5176
5211
|
get headers() {
|
|
5212
|
+
const baseHeaders = {
|
|
5213
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
5214
|
+
"Content-Type": this.contentType,
|
|
5215
|
+
Accept: this.accept
|
|
5216
|
+
};
|
|
5217
|
+
if (this.sameHostname()) {
|
|
5218
|
+
baseHeaders["X-CSRF-Token"] = this.csrfToken;
|
|
5219
|
+
}
|
|
5177
5220
|
return compact(
|
|
5178
|
-
Object.assign(
|
|
5179
|
-
{
|
|
5180
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
5181
|
-
"X-CSRF-Token": this.csrfToken,
|
|
5182
|
-
"Content-Type": this.contentType,
|
|
5183
|
-
Accept: this.accept
|
|
5184
|
-
},
|
|
5185
|
-
this.additionalHeaders
|
|
5186
|
-
)
|
|
5221
|
+
Object.assign(baseHeaders, this.additionalHeaders)
|
|
5187
5222
|
);
|
|
5188
5223
|
}
|
|
5189
5224
|
get csrfToken() {
|
|
@@ -5207,6 +5242,8 @@ var FetchRequest = class {
|
|
|
5207
5242
|
return "text/vnd.turbo-stream.html, text/html, application/xhtml+xml";
|
|
5208
5243
|
case "json":
|
|
5209
5244
|
return "application/json, application/vnd.api+json";
|
|
5245
|
+
case "script":
|
|
5246
|
+
return "text/javascript, application/javascript";
|
|
5210
5247
|
default:
|
|
5211
5248
|
return "*/*";
|
|
5212
5249
|
}
|
|
@@ -5241,6 +5278,12 @@ var FetchRequest = class {
|
|
|
5241
5278
|
get redirect() {
|
|
5242
5279
|
return this.options.redirect || "follow";
|
|
5243
5280
|
}
|
|
5281
|
+
get credentials() {
|
|
5282
|
+
return this.options.credentials || "same-origin";
|
|
5283
|
+
}
|
|
5284
|
+
get keepalive() {
|
|
5285
|
+
return this.options.keepalive || false;
|
|
5286
|
+
}
|
|
5244
5287
|
get additionalHeaders() {
|
|
5245
5288
|
return this.options.headers || {};
|
|
5246
5289
|
}
|
|
@@ -5256,7 +5299,7 @@ var FetchRequest = class {
|
|
|
5256
5299
|
var request = (verb, url, payload, headers) => {
|
|
5257
5300
|
const req = new FetchRequest(verb, url, {
|
|
5258
5301
|
headers: { Accept: "application/json", ...headers },
|
|
5259
|
-
|
|
5302
|
+
...{ query: payload }
|
|
5260
5303
|
});
|
|
5261
5304
|
return req.perform().then((response) => {
|
|
5262
5305
|
if (response.response.ok) {
|
|
@@ -5466,6 +5509,8 @@ var FormController = class _FormController {
|
|
|
5466
5509
|
}
|
|
5467
5510
|
}
|
|
5468
5511
|
submit(event) {
|
|
5512
|
+
if (this.controllers.length === 0 && !this.hasUploadErrors())
|
|
5513
|
+
return;
|
|
5469
5514
|
event.preventDefault();
|
|
5470
5515
|
this.submitted = true;
|
|
5471
5516
|
this.startNextController();
|
|
@@ -5814,11 +5859,13 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5814
5859
|
set value(val) {
|
|
5815
5860
|
const newValue = val || [];
|
|
5816
5861
|
if (JSON.stringify(this.value) !== JSON.stringify(newValue)) {
|
|
5817
|
-
this.files = newValue.map((signedId) =>
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5862
|
+
this.files = newValue.map((signedId) => {
|
|
5863
|
+
const attachmentFile = document.createElement("attachment-file");
|
|
5864
|
+
attachmentFile.name = this.name;
|
|
5865
|
+
attachmentFile.preview = this.preview;
|
|
5866
|
+
attachmentFile.signedId = signedId;
|
|
5867
|
+
return attachmentFile;
|
|
5868
|
+
});
|
|
5822
5869
|
}
|
|
5823
5870
|
}
|
|
5824
5871
|
// For form-persistence: store complete attachment data (not just signed_ids)
|
|
@@ -5912,12 +5959,12 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5912
5959
|
return this.disabled || !!this.el.closest("fieldset[disabled]");
|
|
5913
5960
|
}
|
|
5914
5961
|
render() {
|
|
5915
|
-
return h(Host, { key: "
|
|
5962
|
+
return h(Host, { key: "4ef61015f6e0abb87d50a3f9ff8e39f01dc44ead" }, h("input", { key: "fc02ff04ff705c2c26ef6bc9e21e23435eefe0d6", ref: (el) => this.fileInput = el, type: "file", multiple: this.multiple, accept: this.accepts, required: this.required && this.files.length === 0, disabled: this.isDisabled, onChange: () => this.handleFileInputChange(), style: {
|
|
5916
5963
|
opacity: "0.01",
|
|
5917
5964
|
width: "1px",
|
|
5918
5965
|
height: "1px",
|
|
5919
5966
|
zIndex: "-999"
|
|
5920
|
-
} }), h("file-drop", { key: "
|
|
5967
|
+
} }), h("file-drop", { key: "20ad744ab366a7af2404711a025240515198815f", onClick: () => this.fileInput?.click(), onDrop: this.handleDrop }, h("p", { key: "c5289e9d5b03ced01d95f2d2152ab06dfaaaa8fb", part: "title" }, h("strong", { key: "ed48073458e73fad729caebc1eea8c1ed31fd021" }, "Choose ", this.multiple ? "files" : "file", " "), h("span", { key: "daa4fbd6f33180060a306fdb5ea469d2a68a141b" }, "or drag ", this.multiple ? "them" : "it", " here.")), h("div", { key: "83a5aaf4ee4bc0d7c2917a781ef77568ce9fb9a6", class: `media-preview ${this.multiple ? "-stacked" : ""}` }, h("slot", { key: "8ce39f9805a8f292cc08c9d77c8f91d2754152f7" }))));
|
|
5921
5968
|
}
|
|
5922
5969
|
componentDidRender() {
|
|
5923
5970
|
if (this.files.length === 0) {
|
data/input-attachment/bun.lockb
CHANGED
|
Binary file
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Component, Element, Prop, Listen, Host, h, forceUpdate } from '@stencil/core';
|
|
2
2
|
import FormController from "./form-controller"
|
|
3
|
-
import { AttachmentFile } from "../attachment-file/attachment-file"
|
|
4
3
|
import { arrayRemove } from "../../utils/utils"
|
|
5
4
|
import '@botandrose/file-drop'
|
|
6
5
|
import '@botandrose/progress-bar'
|
|
@@ -125,11 +124,13 @@ export class InputAttachment {
|
|
|
125
124
|
set value(val) {
|
|
126
125
|
const newValue = val || []
|
|
127
126
|
if(JSON.stringify(this.value) !== JSON.stringify(newValue)) { // this is insane. javascript is fucking garbage.
|
|
128
|
-
this.files = newValue.map(signedId =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
this.files = newValue.map(signedId => {
|
|
128
|
+
const attachmentFile = document.createElement('attachment-file') as any
|
|
129
|
+
attachmentFile.name = this.name
|
|
130
|
+
attachmentFile.preview = this.preview
|
|
131
|
+
attachmentFile.signedId = signedId
|
|
132
|
+
return attachmentFile
|
|
133
|
+
})
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
|
|
@@ -24,7 +24,11 @@ module Bard::AttachmentField::TestHelper
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def find_field(session, label)
|
|
27
|
-
label_element =
|
|
27
|
+
label_element = begin
|
|
28
|
+
session.find("label", exact_text: label, visible: :all, match: :first)
|
|
29
|
+
rescue Capybara::ElementNotFound
|
|
30
|
+
session.find("label", text: /^#{Regexp.escape(label)}/, visible: :all, match: :first)
|
|
31
|
+
end
|
|
28
32
|
element_id = label_element[:for]
|
|
29
33
|
session.find("input-attachment##{element_id}")
|
|
30
34
|
end
|
|
@@ -34,7 +38,7 @@ module Bard::AttachmentField::TestHelper
|
|
|
34
38
|
def attach_files(session, element_id, file_paths)
|
|
35
39
|
session.execute_script("document.body.insertAdjacentHTML('beforeend', '<input type=\"file\" id=\"_cdp_file_helper\" multiple style=\"display:none\">')")
|
|
36
40
|
|
|
37
|
-
temp_input = session.find("#_cdp_file_helper", visible: :all)
|
|
41
|
+
temp_input = session.document.find("#_cdp_file_helper", visible: :all)
|
|
38
42
|
temp_input.native.node.select_file(file_paths)
|
|
39
43
|
|
|
40
44
|
session.execute_script(<<~JS)
|
|
@@ -47,12 +51,43 @@ module Bard::AttachmentField::TestHelper
|
|
|
47
51
|
JS
|
|
48
52
|
end
|
|
49
53
|
|
|
54
|
+
def wait_for_files(session, element_id, minimum, timeout: 15)
|
|
55
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
56
|
+
loop do
|
|
57
|
+
count = session.evaluate_script("document.getElementById('#{element_id}').querySelectorAll('attachment-file').length")
|
|
58
|
+
break if count >= minimum
|
|
59
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
60
|
+
raise "Expected at least #{minimum} attachment-file(s) in ##{element_id}, found #{count} after #{timeout}s" if elapsed > timeout
|
|
61
|
+
sleep 0.1
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def wait_for_no_files(session, element_id, selector = "attachment-file", timeout: 10)
|
|
66
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
67
|
+
loop do
|
|
68
|
+
count = session.evaluate_script("document.getElementById('#{element_id}').querySelectorAll('#{selector}').length")
|
|
69
|
+
break if count == 0
|
|
70
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
71
|
+
raise "Expected no #{selector} in ##{element_id}, found #{count} after #{timeout}s" if elapsed > timeout
|
|
72
|
+
sleep 0.1
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
50
76
|
def wait_for_upload(session, element_id, timeout: 30)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
77
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
78
|
+
loop do
|
|
79
|
+
result = session.evaluate_script(<<~JS)
|
|
80
|
+
(() => {
|
|
81
|
+
const files = document.getElementById('#{element_id}').querySelectorAll('attachment-file');
|
|
82
|
+
return Array.from(files).map(e => ({ state: e.getAttribute('state'), value: e.value }));
|
|
83
|
+
})()
|
|
54
84
|
JS
|
|
55
|
-
|
|
85
|
+
states_done = result.all? { |f| f["state"] == "complete" || f["state"] == "error" }
|
|
86
|
+
values_set = result.all? { |f| f["state"] == "error" || (f["value"] && !f["value"].empty?) }
|
|
87
|
+
break if states_done && values_set
|
|
88
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
89
|
+
raise "Uploads not complete after #{timeout}s (files=#{result})" if elapsed > timeout
|
|
90
|
+
sleep 0.1
|
|
56
91
|
end
|
|
57
92
|
end
|
|
58
93
|
|
|
@@ -116,6 +151,7 @@ class Chop::Form::AttachmentField < Chop::Form::Field
|
|
|
116
151
|
end
|
|
117
152
|
|
|
118
153
|
Bard::AttachmentField::TestHelper.attach_files(session, field[:id], file_paths)
|
|
154
|
+
Bard::AttachmentField::TestHelper.wait_for_files(session, field[:id], file_paths.length)
|
|
119
155
|
Bard::AttachmentField::TestHelper.wait_for_upload(session, field[:id])
|
|
120
156
|
end
|
|
121
157
|
end
|
|
@@ -123,29 +159,32 @@ end
|
|
|
123
159
|
# Step definitions
|
|
124
160
|
|
|
125
161
|
When "I attach the file {string} to {string}" do |path, field|
|
|
126
|
-
element = Bard::AttachmentField::TestHelper.find_field(page, field)
|
|
127
162
|
file_path_full = Bard::AttachmentField::TestHelper.resolve_fixture_path(path)
|
|
128
163
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
164
|
+
begin
|
|
165
|
+
element = Bard::AttachmentField::TestHelper.find_field(page, field)
|
|
166
|
+
rescue Capybara::ElementNotFound
|
|
167
|
+
attach_file field, file_path_full
|
|
168
|
+
next
|
|
133
169
|
end
|
|
134
170
|
|
|
171
|
+
Bard::AttachmentField::TestHelper.attach_files(page, element[:id], [file_path_full])
|
|
172
|
+
Bard::AttachmentField::TestHelper.wait_for_files(page, element[:id], 1)
|
|
135
173
|
Bard::AttachmentField::TestHelper.wait_for_upload(page, element[:id])
|
|
136
174
|
end
|
|
137
175
|
|
|
138
176
|
When "I attach the following files to {string}:" do |field, table|
|
|
139
177
|
files = table.raw.map(&:first).map { |filename| Bard::AttachmentField::TestHelper.resolve_fixture_path(filename) }
|
|
140
178
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
element.all("attachment-file", minimum: files.length)
|
|
179
|
+
begin
|
|
180
|
+
element = Bard::AttachmentField::TestHelper.find_field(page, field)
|
|
181
|
+
rescue Capybara::ElementNotFound
|
|
182
|
+
attach_file field, files
|
|
183
|
+
next
|
|
147
184
|
end
|
|
148
185
|
|
|
186
|
+
Bard::AttachmentField::TestHelper.attach_files(page, element[:id], files)
|
|
187
|
+
Bard::AttachmentField::TestHelper.wait_for_files(page, element[:id], files.length)
|
|
149
188
|
Bard::AttachmentField::TestHelper.wait_for_upload(page, element[:id], timeout: 60)
|
|
150
189
|
end
|
|
151
190
|
|
|
@@ -161,11 +200,7 @@ When "I remove the file from {string}" do |field|
|
|
|
161
200
|
removeLink.click();
|
|
162
201
|
})(arguments[0])
|
|
163
202
|
JS
|
|
164
|
-
|
|
165
|
-
page.document.synchronize(5, errors: [Capybara::ElementNotFound]) do
|
|
166
|
-
fresh_element = page.find("##{element_id}")
|
|
167
|
-
expect(fresh_element).to have_no_css("attachment-file")
|
|
168
|
-
end
|
|
203
|
+
Bard::AttachmentField::TestHelper.wait_for_no_files(page, element_id)
|
|
169
204
|
end
|
|
170
205
|
|
|
171
206
|
When "I remove {string} from {string}" do |filename, field|
|
|
@@ -180,11 +215,21 @@ When "I remove {string} from {string}" do |filename, field|
|
|
|
180
215
|
removeLink.click();
|
|
181
216
|
})(arguments[0], arguments[1])
|
|
182
217
|
JS
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
218
|
+
Bard::AttachmentField::TestHelper.wait_for_no_files(page, element_id, "attachment-file[filename='#{filename}']")
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
When "I remove {string} from the {string} field" do |filename, field|
|
|
222
|
+
element = Bard::AttachmentField::TestHelper.find_field(page, field)
|
|
223
|
+
element_id = element[:id]
|
|
224
|
+
page.execute_script(<<~JS, element_id, filename)
|
|
225
|
+
((elementId, filename) => {
|
|
226
|
+
const host = document.getElementById(elementId);
|
|
227
|
+
const attachmentFile = host.querySelector(`attachment-file[filename='${filename}']`);
|
|
228
|
+
const removeLink = attachmentFile.shadowRoot.querySelector('a.remove-media');
|
|
229
|
+
removeLink.click();
|
|
230
|
+
})(arguments[0], arguments[1])
|
|
231
|
+
JS
|
|
232
|
+
Bard::AttachmentField::TestHelper.wait_for_no_files(page, element_id, "attachment-file[filename='#{filename}']")
|
|
188
233
|
end
|
|
189
234
|
|
|
190
235
|
When "I drag the file {string} onto the {string} attachment field" do |path, field|
|
|
@@ -227,6 +272,13 @@ Then "I should see a preview of {string} for the {string} field" do |filename, f
|
|
|
227
272
|
expect(element.find("attachment-file")[:filename]).to eq(filename)
|
|
228
273
|
end
|
|
229
274
|
|
|
275
|
+
Then "I should see the following media previews for the {string} field:" do |field, table|
|
|
276
|
+
element = Bard::AttachmentField::TestHelper.find_field(page, field)
|
|
277
|
+
element.assert_selector("attachment-file", minimum: table.raw.length)
|
|
278
|
+
actual = Bard::AttachmentField::TestHelper.get_files(element).map { |f| [f] }
|
|
279
|
+
table.diff! actual
|
|
280
|
+
end
|
|
281
|
+
|
|
230
282
|
When "I follow the {string} download link for {string}" do |filename, field|
|
|
231
283
|
element = Bard::AttachmentField::TestHelper.find_field(page, field)
|
|
232
284
|
# Click the download link inside attachment-file's shadow DOM
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bard-attachment_field
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Micah Geisel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|