Skip to content

Commit 92b39a9

Browse files
committed
allow form and button attributes in helper methods
1 parent e1d495f commit 92b39a9

3 files changed

Lines changed: 194 additions & 10 deletions

File tree

.tool-versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ruby 3.4.8

lib/devise/webauthn/helpers/credentials_helper.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
module Devise
55
module Webauthn
66
module CredentialsHelper
7-
def passkey_creation_form_for(resource, form_classes: nil, &block)
7+
def passkey_creation_form_for(resource, form_attributes: {}, &block)
88
form_with(
99
url: passkeys_path(resource),
1010
method: :post,
11-
class: form_classes
11+
**form_attributes
1212
) do |f|
1313
tag.webauthn_create(data: { options_url: passkey_registration_options_path(resource) }) do
1414
concat f.hidden_field(:public_key_credential, data: { webauthn_target: "response" })
@@ -17,25 +17,25 @@ def passkey_creation_form_for(resource, form_classes: nil, &block)
1717
end
1818
end
1919

20-
def login_with_passkey_button(text = nil, session_path:, button_classes: nil, form_classes: nil, &block)
20+
def login_with_passkey_button(text = nil, session_path:, button_attributes: {}, form_attributes: {}, &block)
2121
form_with(
2222
url: session_path,
2323
method: :post,
24-
class: form_classes
24+
**form_attributes
2525
) do |f|
2626
tag.webauthn_get(data: { options_url: passkey_authentication_options_path(resource) }) do
2727
concat f.hidden_field(:public_key_credential, data: { webauthn_target: "response" })
2828

29-
concat f.button(text, type: "submit", class: button_classes, &block)
29+
concat f.button(text, type: "submit", **button_attributes, &block)
3030
end
3131
end
3232
end
3333

34-
def security_key_creation_form_for(resource, form_classes: nil, &block)
34+
def security_key_creation_form_for(resource, form_attributes: {}, &block)
3535
form_with(
3636
url: second_factor_webauthn_credentials_path(resource),
3737
method: :post,
38-
class: form_classes
38+
**form_attributes
3939
) do |f|
4040
tag.webauthn_create(
4141
data: { options_url: security_key_registration_options_path(resource) }
@@ -46,15 +46,15 @@ def security_key_creation_form_for(resource, form_classes: nil, &block)
4646
end
4747
end
4848

49-
def login_with_security_key_button(text = nil, resource:, button_classes: nil, form_classes: nil, &block)
49+
def login_with_security_key_button(text = nil, resource:, button_attributes: {}, form_attributes: {}, &block)
5050
form_with(
5151
url: two_factor_authentication_path(resource),
5252
method: :post,
53-
class: form_classes
53+
**form_attributes
5454
) do |f|
5555
tag.webauthn_get(data: { options_url: security_key_authentication_options_path(resource) }) do
5656
concat f.hidden_field(:public_key_credential, data: { webauthn_target: "response" })
57-
concat f.button(text, type: "submit", class: button_classes, &block)
57+
concat f.button(text, type: "submit", **button_attributes, &block)
5858
end
5959
end
6060
end
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe Devise::Webauthn::CredentialsHelper, type: :helper do
4+
let(:account) do
5+
Account.create!(email: "test@example.com", password: "password123", webauthn_id: "test-webauthn-id")
6+
end
7+
8+
before do
9+
allow(helper).to receive(:session).and_return({})
10+
end
11+
12+
def parsed(html)
13+
Capybara.string(html)
14+
end
15+
16+
describe "#passkey_creation_form_for" do
17+
before do
18+
allow(helper).to receive(:passkeys_path).with(account).and_return("/passkeys")
19+
allow(helper).to receive(:passkey_registration_options_path).with(account).and_return("/passkey_options")
20+
end
21+
22+
it "renders form with default attributes" do
23+
html = helper.passkey_creation_form_for(account) { |_f| "" }
24+
25+
expect(parsed(html)).to have_css('form[action="/passkeys"][method="post"]')
26+
end
27+
28+
it "renders form with custom form_attributes" do
29+
html = helper.passkey_creation_form_for(
30+
account,
31+
form_attributes: {
32+
class: "my-form",
33+
id: "passkey-form",
34+
data: { turbo: false }
35+
}
36+
) { |_f| "" }
37+
38+
expect(parsed(html)).to have_css('form.my-form#passkey-form[data-turbo="false"]')
39+
end
40+
end
41+
42+
describe "#login_with_passkey_button" do
43+
before do
44+
allow(helper).to receive_messages(
45+
passkey_authentication_options_path: "/passkey_auth_options",
46+
resource: account
47+
)
48+
end
49+
50+
it "renders form with default attributes" do
51+
html = helper.login_with_passkey_button("Login", session_path: "/sessions")
52+
53+
expect(parsed(html)).to have_css('form[action="/sessions"][method="post"]')
54+
expect(parsed(html)).to have_css('button[type="submit"]', text: "Login")
55+
end
56+
57+
it "renders form with custom form_attributes" do
58+
html = helper.login_with_passkey_button(
59+
"Login",
60+
session_path: "/sessions",
61+
form_attributes: {
62+
class: "auth-form",
63+
id: "passkey-login",
64+
data: { controller: "auth" }
65+
}
66+
)
67+
68+
expect(parsed(html)).to have_css('form.auth-form#passkey-login[data-controller="auth"]')
69+
end
70+
71+
it "renders button with custom button_attributes" do
72+
html = helper.login_with_passkey_button(
73+
"Login",
74+
session_path: "/sessions",
75+
button_attributes: {
76+
class: "btn btn-primary",
77+
id: "passkey-btn",
78+
data: { disable_with: "Authenticating..." }
79+
}
80+
)
81+
82+
expect(parsed(html)).to have_css('button.btn.btn-primary#passkey-btn[data-disable-with="Authenticating..."]')
83+
end
84+
85+
it "renders with both form_attributes and button_attributes" do
86+
html = helper.login_with_passkey_button(
87+
"Login",
88+
session_path: "/sessions",
89+
form_attributes: { class: "passkey-form", data: { turbo: false } },
90+
button_attributes: { class: "submit-btn", disabled: true }
91+
)
92+
93+
expect(parsed(html)).to have_css('form.passkey-form[data-turbo="false"]')
94+
expect(parsed(html)).to have_css("button.submit-btn[disabled]")
95+
end
96+
end
97+
98+
describe "#security_key_creation_form_for" do
99+
before do
100+
allow(helper).to receive(:second_factor_webauthn_credentials_path)
101+
.with(account).and_return("/security_keys")
102+
allow(helper).to receive(:security_key_registration_options_path)
103+
.with(account).and_return("/security_key_options")
104+
end
105+
106+
it "renders form with default attributes" do
107+
html = helper.security_key_creation_form_for(account) { |_f| "" }
108+
109+
expect(parsed(html)).to have_css('form[action="/security_keys"][method="post"]')
110+
end
111+
112+
it "renders form with custom form_attributes" do
113+
html = helper.security_key_creation_form_for(
114+
account,
115+
form_attributes: {
116+
class: "webauthn-form",
117+
id: "security-key-registration",
118+
data: { turbo: false }
119+
}
120+
) { |_f| "" }
121+
122+
expect(parsed(html)).to have_css('form.webauthn-form#security-key-registration[data-turbo="false"]')
123+
end
124+
end
125+
126+
describe "#login_with_security_key_button" do
127+
before do
128+
allow(helper).to receive(:two_factor_authentication_path)
129+
.with(account).and_return("/two_factor")
130+
allow(helper).to receive(:security_key_authentication_options_path)
131+
.with(account).and_return("/security_key_auth_options")
132+
end
133+
134+
it "renders form with default attributes" do
135+
html = helper.login_with_security_key_button("Authenticate", resource: account)
136+
137+
expect(parsed(html)).to have_css('form[action="/two_factor"][method="post"]')
138+
expect(parsed(html)).to have_css('button[type="submit"]', text: "Authenticate")
139+
end
140+
141+
it "renders form with custom form_attributes" do
142+
html = helper.login_with_security_key_button(
143+
"Authenticate",
144+
resource: account,
145+
form_attributes: {
146+
class: "mfa-form",
147+
id: "security-key-auth",
148+
data: { turbo_method: "post" }
149+
}
150+
)
151+
152+
expect(parsed(html)).to have_css('form.mfa-form#security-key-auth[data-turbo-method="post"]')
153+
end
154+
155+
it "renders button with custom button_attributes" do
156+
html = helper.login_with_security_key_button(
157+
"Authenticate",
158+
resource: account,
159+
button_attributes: {
160+
class: "btn btn-secondary",
161+
id: "security-key-btn",
162+
data: { action: "click->webauthn#authenticate" }
163+
}
164+
)
165+
166+
expect(parsed(html)).to have_css(
167+
'button.btn.btn-secondary#security-key-btn[data-action="click->webauthn#authenticate"]'
168+
)
169+
end
170+
171+
it "renders with both form_attributes and button_attributes" do
172+
html = helper.login_with_security_key_button(
173+
"Authenticate",
174+
resource: account,
175+
form_attributes: { class: "two-factor-form", data: { controller: "mfa" } },
176+
button_attributes: { class: "auth-btn", data: { testid: "security-key-submit" } }
177+
)
178+
179+
expect(parsed(html)).to have_css('form.two-factor-form[data-controller="mfa"]')
180+
expect(parsed(html)).to have_css('button.auth-btn[data-testid="security-key-submit"]')
181+
end
182+
end
183+
end

0 commit comments

Comments
 (0)