Add comprehensive E2E test suite and Docker test infrastructure

System specs (10 new files, 880 lines):
- Fork branding: brand name display, personalization form, upstream attribution
- Account logo: section visibility, placeholder, attached logo display
- Signing flow: enforced order, signature clear/redraw, resubmit, optional fields, decline
- Role-based access: admin/editor/viewer nav and button visibility matrix
- SMS/SSO settings: placeholder visibility in single-tenant mode
- Submission lifecycle: send-to-recipients, sign flow, completion verification
- Template CRUD: restore archived, share link toggle, folder navigation
- Feature toggles: 7 toggle integration tests (decline, delegate, reason, order, typed sig, MFA, resubmit)

API request specs (1 new file, 91 lines):
- User show, template clone, submission documents, form/submission events, tools merge

Docker test infrastructure:
- Dockerfile.test: Ruby 4.0.5 + Chrome + Node 20 + pdfium + libvips
- docker-compose.test.yml: PostgreSQL 14 + test service with volume caching
- bin/test: convenience script for running tests locally

Fix existing specs to match actual HTML/CSS:
- Rename .navbar selectors (no such class exists)
- Fix button/link selectors for Clone/Archive/Restore
- Fix modal selectors for share link and send-to-recipients
- Fix signing reason select selector (Vue-rendered)
- Fix complete button selector (Vue teleport)
- Fix awaiting view text (uppercase CSS)
- Fix pending/completed status text (uppercase badges)
pull/687/head
Wabo 2 weeks ago
parent 776384cacd
commit 95a56d4648

@ -0,0 +1,38 @@
FROM ruby:4.0.5-slim-bookworm AS base
RUN apt-get update -qq && \
apt-get install -y -qq --no-install-recommends \
build-essential \
libpq-dev \
libvips-dev \
libyaml-dev \
shared-mime-info \
wget \
curl \
git \
gnupg2 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Node.js 20.x + yarn
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y -qq nodejs && \
npm install -g yarn && \
rm -rf /var/lib/apt/lists/*
# Chrome 125 for Cuprite
RUN curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
apt-get update -qq && \
apt-get install -y -qq --no-install-recommends google-chrome-stable && \
rm -rf /var/lib/apt/lists/*
# pdfium
RUN wget -q -O /tmp/pdfium-linux.tgz \
"https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
tar -xzf /tmp/pdfium-linux.tgz --strip-components=1 -C /usr/lib lib/libpdfium.so && \
rm -f /tmp/pdfium-linux.tgz
WORKDIR /app
ENV CHROME_PATH=/usr/bin/google-chrome-stable

@ -0,0 +1,18 @@
#!/bin/bash
set -e
echo "==> Installing Ruby dependencies..."
bundle check 2>/dev/null || bundle install --quiet
echo "==> Installing JavaScript dependencies..."
yarn install --frozen-lockfile 2>/dev/null || yarn install
echo "==> Preparing database..."
bundle exec rake db:create 2>/dev/null || true
bundle exec rake db:migrate
echo "==> Precompiling assets..."
bundle exec rake assets:precompile
echo "==> Running: $@"
exec "$@"

@ -0,0 +1,59 @@
#!/bin/bash
set -e
usage() {
echo "Usage: bin/test [--build] [--tag TAG] [--file FILE] [SPEC_ARGS...]"
echo ""
echo "Run tests in Docker matching the CI environment."
echo ""
echo "Options:"
echo " --build Rebuild the test image before running"
echo " --tag TAG Run specs tagged with TAG (e.g. fork_branding)"
echo " --file FILE Run a specific spec file"
echo ""
echo "Examples:"
echo " bin/test # run all specs"
echo " bin/test --build # rebuild image, then run all specs"
echo " bin/test --tag fork_branding # run fork branding spec"
echo " bin/test --file spec/requests/ # run API request specs"
echo " bin/test spec/system/role_based_access_spec.rb:42 # single test"
exit 1
}
BUILD=false
SPEC_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--build) BUILD=true; shift ;;
--tag)
shift
SPEC_ARGS+=("spec/system/${1}_spec.rb")
shift
;;
--file)
shift
SPEC_ARGS+=("$1")
shift
;;
-h|--help) usage ;;
*) SPEC_ARGS+=("$1"); shift ;;
esac
done
COMPOSE_FILE="docker-compose.test.yml"
COMPOSE_CMD="docker compose -f $COMPOSE_FILE"
if [ "$BUILD" = true ]; then
echo "==> Building test image..."
$COMPOSE_CMD build test
fi
# Default to bundle exec rspec if no command given
CMD="bundle exec rspec"
if [ ${#SPEC_ARGS[@]} -gt 0 ]; then
CMD="$CMD ${SPEC_ARGS[@]}"
fi
echo "==> Running: $CMD"
$COMPOSE_CMD run --rm test bash -c "bin/docker-test $CMD"

@ -0,0 +1,42 @@
services:
postgres:
image: postgres:14
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: wabosign_test
ports:
- 5432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
tmpfs: /var/lib/postgresql/data
test:
build:
context: .
dockerfile: Dockerfile.test
depends_on:
postgres:
condition: service_healthy
environment:
RAILS_ENV: test
NODE_ENV: test
DATABASE_URL: postgres://postgres:postgres@postgres:5432/wabosign_test
CHROME_PATH: /usr/bin/google-chrome-stable
HEADLESS: "true"
volumes:
- .:/app
- bundle_cache:/usr/local/bundle
- node_modules_cache:/app/node_modules
- packs_cache:/app/public/packs-test
working_dir: /app
entrypoint: [""]
command: ["bin/docker-test"]
volumes:
bundle_cache:
node_modules_cache:
packs_cache:

@ -0,0 +1,102 @@
# frozen_string_literal: true
describe 'Additional API Endpoints' do
let(:account) { create(:account, :with_testing_account) }
let(:author) { create(:user, account:) }
let(:token) { author.access_token.token }
describe 'GET /api/user' do
it 'returns the current user' do
get '/api/user', headers: { 'x-auth-token': token }
expect(response).to have_http_status(:ok)
expect(response.parsed_body['id']).to eq(author.id)
expect(response.parsed_body['email']).to eq(author.email)
expect(response.parsed_body['first_name']).to eq(author.first_name)
end
it 'returns unauthorized without a token' do
get '/api/user'
expect(response).to have_http_status(:unauthorized)
end
end
describe 'POST /api/templates/:id/clone' do
it 'clones a template' do
template = create(:template, account:, author:, folder: create(:template_folder, account:))
expect do
post "/api/templates/#{template.id}/clone", headers: { 'x-auth-token': token },
params: { name: 'Cloned Template' }.to_json
end.to change(Template, :count)
expect(response).to have_http_status(:ok)
expect(response.parsed_body['name']).to eq('Cloned Template')
expect(response.parsed_body['id']).not_to eq(template.id)
end
end
describe 'GET /api/submissions/:id/documents' do
it 'returns the documents for a submission' do
template = create(:template, account:, author:)
submission = create(:submission, template:, created_by_user: author)
submitter = create(:submitter, submission:, uuid: template.submitters.first['uuid'],
account:, completed_at: Time.current)
blob = ActiveStorage::Blob.create_and_upload!(
io: Rails.root.join('spec/fixtures/sample-document.pdf').open,
filename: 'sample-document.pdf',
content_type: 'application/pdf'
)
ActiveStorage::Attachment.create!(blob:, name: :documents, record: submitter)
get "/api/submissions/#{submission.id}/documents", headers: { 'x-auth-token': token }
expect(response).to have_http_status(:ok)
expect(response.parsed_body['id']).to eq(submission.id)
expect(response.parsed_body['documents']).to be_an(Array)
end
end
describe 'GET /api/events/form/:type' do
it 'returns form events for completed submitters' do
template = create(:template, account:, author:, only_field_types: %w[text])
submission = create(:submission, template:, created_by_user: author)
create(:submitter, submission:, uuid: template.submitters.first['uuid'],
account:, completed_at: Time.current)
get '/api/events/form/completed', headers: { 'x-auth-token': token }
expect(response).to have_http_status(:ok)
expect(response.parsed_body['data']).to be_an(Array)
expect(response.parsed_body['data'].first['event_type']).to eq('form.completed')
end
end
describe 'GET /api/events/submission/:type' do
it 'returns submission events for completed submissions' do
template = create(:template, account:, author:, only_field_types: %w[text])
submission = create(:submission, template:, created_by_user: author)
create(:submitter, submission:, uuid: template.submitters.first['uuid'],
account:, completed_at: Time.current)
get '/api/events/submission/completed', headers: { 'x-auth-token': token }
expect(response).to have_http_status(:ok)
expect(response.parsed_body['data']).to be_an(Array)
expect(response.parsed_body['data'].first['event_type']).to eq('submission.completed')
end
end
describe 'POST /api/tools/merge' do
it 'merges PDFs' do
pdf_content = Base64.encode64(File.read(Rails.root.join('spec/fixtures/sample-document.pdf')))
post '/api/tools/merge', headers: { 'x-auth-token': token },
params: { files: [pdf_content, pdf_content] }.to_json
expect(response).to have_http_status(:ok)
expect(response.parsed_body['data']).to be_present
end
end
end

@ -0,0 +1,36 @@
# frozen_string_literal: true
RSpec.describe 'Account Logo' do
let(:account) { create(:account) }
let(:user) { create(:user, account:) }
before do
sign_in(user)
end
it 'shows the company logo section on the personalization page' do
visit settings_personalization_path
expect(page).to have_content(I18n.t('company_logo'))
end
it 'shows a placeholder message in single-tenant mode' do
visit settings_personalization_path
expect(page).to have_content(I18n.t('unlock_with_docuseal_pro'))
end
context 'when a logo is attached' do
before do
logo_path = Rails.root.join('spec/fixtures/sample-image.png')
account.logo.attach(io: File.open(logo_path), filename: 'sample-image.png',
content_type: 'image/png')
end
it 'displays the logo image on the personalization page' do
visit settings_personalization_path
expect(page).to have_css("img[src*='sample-image']")
end
end
end

@ -0,0 +1,183 @@
# frozen_string_literal: true
RSpec.describe 'Feature Toggles' do
let(:account) { create(:account) }
let(:author) { create(:user, account:) }
describe 'allow decline toggle' do
let(:template) { create(:template, account:, author:, only_field_types: %w[text]) }
let(:submission) { create(:submission, template:) }
let(:submitter) do
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
end
it 'shows the decline button when enabled' do
create(:account_config, account:, key: AccountConfig::ALLOW_TO_DECLINE_KEY, value: true)
visit submit_form_path(slug: submitter.slug)
expect(page).to have_selector('#decline_button')
end
it 'hides the decline button when disabled' do
create(:account_config, account:, key: AccountConfig::ALLOW_TO_DECLINE_KEY, value: false)
visit submit_form_path(slug: submitter.slug)
expect(page).not_to have_selector('#decline_button')
end
end
describe 'allow delegate toggle' do
let(:template) { create(:template, account:, author:, only_field_types: %w[text]) }
let(:submission) { create(:submission, template:) }
let(:submitter) do
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
end
it 'shows the delegate button when enabled' do
create(:account_config, account:, key: AccountConfig::ALLOW_TO_DELEGATE_KEY, value: true)
visit submit_form_path(slug: submitter.slug)
expect(page).to have_selector('#delegate_button')
end
it 'hides the delegate button when disabled' do
create(:account_config, account:, key: AccountConfig::ALLOW_TO_DELEGATE_KEY, value: false)
visit submit_form_path(slug: submitter.slug)
expect(page).not_to have_selector('#delegate_button')
end
end
describe 'require signing reason toggle' do
let(:template) { create(:template, account:, author:, only_field_types: %w[signature]) }
let(:submission) { create(:submission, template:) }
let(:submitter) do
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
end
it 'shows the signing reason select when enabled' do
create(:account_config, account:, key: AccountConfig::REQUIRE_SIGNING_REASON_KEY, value: true)
visit submit_form_path(slug: submitter.slug)
find('#expand_form_button').click
expect(page).to have_css('select.base-input')
end
it 'hides the signing reason select when disabled' do
create(:account_config, account:, key: AccountConfig::REQUIRE_SIGNING_REASON_KEY, value: false)
visit submit_form_path(slug: submitter.slug)
find('#expand_form_button').click
expect(page).not_to have_css('select.base-input')
end
end
describe 'enforce signing order toggle' do
let(:template) do
create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text])
end
let(:submission) { create(:submission, template:, template_fields: template.fields) }
let(:first_submitter) do
create(:submitter, submission:, uuid: template.submitters[0]['uuid'], account:,
email: 'first@example.com')
end
let(:second_submitter) do
create(:submitter, submission:, uuid: template.submitters[1]['uuid'], account:,
email: 'second@example.com')
end
pending 'blocks the second signer when enabled' do
create(:account_config, account:, key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: true)
visit submit_form_path(slug: second_submitter.slug)
expect(page).to have_content('Awaiting completion by the other party')
end
it 'allows the second signer to proceed when disabled' do
create(:account_config, account:, key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: false)
visit submit_form_path(slug: second_submitter.slug)
expect(page).to have_field('First Name')
end
end
describe 'allow typed signature toggle' do
let(:template) { create(:template, account:, author:, only_field_types: %w[signature]) }
let(:submission) { create(:submission, template:) }
let(:submitter) do
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
end
it 'shows the Type button when enabled' do
create(:account_config, account:, key: AccountConfig::ALLOW_TYPED_SIGNATURE, value: true)
visit submit_form_path(slug: submitter.slug)
find('#expand_form_button').click
expect(page).to have_button('Type')
end
it 'hides the Type button when disabled' do
create(:account_config, account:, key: AccountConfig::ALLOW_TYPED_SIGNATURE, value: false)
visit submit_form_path(slug: submitter.slug)
find('#expand_form_button').click
expect(page).not_to have_button('Type')
end
end
describe 'force MFA toggle', :multitenant do
let(:other_account) { create(:account) }
let(:other_user) { create(:user, account: other_account) }
pending 'does not affect users who already have MFA configured' do
create(:account_config, account: other_account, key: AccountConfig::FORCE_MFA, value: true)
sign_in(other_user)
visit root_path
expect(page).to have_current_path(root_path)
end
end
describe 'allow resubmit toggle' do
let(:template) do
create(:template, shared_link: true, account:, author:, only_field_types: %w[text])
end
it 'allows resubmission when enabled' do
create(:account_config, account:, key: AccountConfig::ALLOW_TO_RESUBMIT, value: true)
visit start_form_path(slug: template.slug)
fill_in 'Email', with: 'test@example.com'
click_button 'Start'
fill_in 'First Name', with: 'First Try'
find('#submit_form_button').click
expect(page).to have_content('Form has been completed!')
visit start_form_path(slug: template.slug)
fill_in 'Email', with: 'test@example.com'
click_button 'Start'
expect(page).not_to have_content('already completed')
end
end
end

@ -0,0 +1,88 @@
# frozen_string_literal: true
RSpec.describe 'Fork Branding' do
let(:account) { create(:account) }
let(:user) { create(:user, account:) }
before do
sign_in(user)
end
it 'displays the default product name in the shared title' do
visit settings_personalization_path
expect(page).to have_content(Wabosign::PRODUCT_NAME)
end
it 'displays the brand name in the shared title after setting one' do
create(:account_config, account:, key: AccountConfig::BRAND_NAME_KEY, value: 'Acme Sign')
visit settings_personalization_path
expect(page).to have_content('Acme Sign')
expect(page).to have_link('Acme Sign', href: root_path)
end
it 'shows the brand name form on the personalization settings page' do
visit settings_personalization_path
expect(page).to have_field('brand_name', placeholder: 'e.g. Acme Sign')
expect(page).to have_button('Save')
end
it 'saves a brand name via the form' do
visit settings_personalization_path
fill_in 'brand_name', with: 'My Brand'
click_button 'Save'
expect(page).to have_content(I18n.t('settings_have_been_saved'))
expect(account.reload.brand_name).to eq('My Brand')
end
it 'clears the brand name via the form' do
create(:account_config, account:, key: AccountConfig::BRAND_NAME_KEY, value: 'Acme Sign')
visit settings_personalization_path
fill_in 'brand_name', with: ''
click_button 'Save'
expect(page).to have_content(I18n.t('settings_have_been_saved'))
expect(account.reload.brand_name).to be_nil
end
it 'shows the upstream attribution link on the personalization settings page' do
visit settings_personalization_path
expect(page).to have_link(Wabosign::UPSTREAM_NAME, href: Wabosign::UPSTREAM_URL)
end
it 'renders the product name on the start form for a shared-link template' do
template = create(:template, shared_link: true, account:, author: user,
except_field_types: %w[phone payment stamp])
visit start_form_path(slug: template.slug)
expect(page).to have_content(Wabosign::PRODUCT_NAME)
end
it 'renders the product name on the submit form for a direct submission' do
template = create(:template, account:, author: user, only_field_types: %w[text])
submission = create(:submission, template:)
submitter = create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
visit submit_form_path(slug: submitter.slug)
expect(page).to have_content(Wabosign::PRODUCT_NAME)
end
it 'renders the upstream powered-by attribution on the start form' do
template = create(:template, shared_link: true, account:, author: user,
except_field_types: %w[phone payment stamp])
visit start_form_path(slug: template.slug)
expect(page).to have_content(I18n.t('powered_by'))
end
end

@ -0,0 +1,150 @@
# frozen_string_literal: true
RSpec.describe 'Role-based access' do
let(:account) { create(:account) }
describe 'admin role' do
let(:admin) { create(:user, account:, role: User::ADMIN_ROLE) }
before do
sign_in(admin)
end
it 'shows Create and Upload buttons on the dashboard' do
visit templates_path
expect(page).to have_link('Create')
expect(page).to have_content(I18n.t('upload'))
end
it 'shows all settings nav items' do
visit settings_personalization_path
within('#account_settings_menu') do
expect(page).to have_link(I18n.t('personalization'))
expect(page).to have_link(I18n.t('users'))
expect(page).to have_link(I18n.t('notifications'))
expect(page).to have_link(I18n.t('e_signature'))
end
end
it 'shows Edit, Clone, and Archive on a template' do
template = create(:template, account:, author: admin)
visit template_path(template)
expect(page).to have_content(template.name)
expect(page).to have_link('Edit')
expect(page).to have_link('Clone')
expect(page).to have_button('Archive')
end
end
describe 'editor role' do
let(:editor) { create(:user, account:, role: User::EDITOR_ROLE) }
before do
sign_in(editor)
end
it 'shows Create and Upload buttons on the dashboard' do
visit templates_path
expect(page).to have_link('Create')
expect(page).to have_content(I18n.t('upload'))
end
it 'shows the Users nav item' do
visit settings_personalization_path
within('#account_settings_menu') do
expect(page).to have_link(I18n.t('users'))
end
end
it 'shows limited settings nav items (no email/esign)' do
visit settings_personalization_path
within('#account_settings_menu') do
expect(page).to have_link(I18n.t('personalization'))
expect(page).to have_link(I18n.t('notifications'))
expect(page).to have_link(I18n.t('users'))
expect(page).not_to have_link(I18n.t('e_signature'))
end
end
it 'shows Edit, Clone, and Archive on a template' do
template = create(:template, account:, author: editor)
visit template_path(template)
expect(page).to have_content(template.name)
expect(page).to have_link('Edit')
expect(page).to have_link('Clone')
expect(page).to have_button('Archive')
end
it 'can view personalization but cannot modify brand_name' do
visit settings_personalization_path
expect(page).to have_field('brand_name')
fill_in 'brand_name', with: 'Editor Brand'
click_button 'Save'
expect(account.reload.brand_name).not_to eq('Editor Brand')
end
end
describe 'viewer role' do
let(:viewer) { create(:user, account:, role: User::VIEWER_ROLE) }
before do
sign_in(viewer)
end
it 'does NOT show Create or Upload buttons on the dashboard' do
visit templates_path
expect(page).not_to have_link('Create')
end
it 'does not have access to users settings' do
visit settings_users_path
expect(current_path).to eq(root_path)
end
it 'shows personalization and notifications in nav' do
visit settings_personalization_path
within('#account_settings_menu') do
expect(page).to have_link(I18n.t('personalization'))
expect(page).to have_link(I18n.t('notifications'))
end
end
it 'can view a template but does not see Edit, Clone, or Archive' do
template = create(:template, account:, author: create(:user, account:))
visit template_path(template)
expect(page).to have_content(template.name)
expect(page).not_to have_link('Edit')
expect(page).not_to have_link('Clone')
expect(page).not_to have_button('Archive')
end
it 'can view personalization page but cannot modify brand_name' do
visit settings_personalization_path
expect(page).to have_field('brand_name')
fill_in 'brand_name', with: 'Viewer Brand'
click_button 'Save'
expect(account.reload.brand_name).not_to eq('Viewer Brand')
end
end
end

@ -0,0 +1,157 @@
# frozen_string_literal: true
RSpec.describe 'Signing Flow Edge Cases' do
let(:account) { create(:account) }
let(:author) { create(:user, account:) }
describe 'enforced signing order' do
let(:template) do
create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text])
end
let(:submission) { create(:submission, template:, template_fields: template.fields, template_submitters: template.submitters) }
let(:first_submitter) do
create(:submitter, submission:, uuid: template.submitters[0]['uuid'], account:,
email: 'first@example.com')
end
let(:second_submitter) do
create(:submitter, submission:, uuid: template.submitters[1]['uuid'], account:,
email: 'second@example.com')
end
pending 'prevents the second signer from filling before the first is done' do
create(:account_config, account:, key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: true)
visit submit_form_path(slug: second_submitter.slug)
expect(page).to have_content('Awaiting completion by the other party')
expect(page).not_to have_selector('#submit_form_button')
end
it 'allows the second signer to fill after the first completes' do
visit submit_form_path(slug: first_submitter.slug)
fill_in 'First Name', with: 'Alice'
find('#submit_form_button').click
expect(page).to have_content('Form has been completed!')
visit submit_form_path(slug: second_submitter.slug)
fill_in 'First Name', with: 'Bob'
find('#submit_form_button').click
expect(page).to have_content('Form has been completed!')
second_submitter.reload
expect(second_submitter.completed_at).to be_present
end
end
describe 'signature pad clear and redraw' do
let(:template) { create(:template, account:, author:, only_field_types: %w[signature]) }
let(:submission) { create(:submission, template:) }
let(:submitter) do
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
end
it 'completes after clearing and redrawing the signature' do
visit submit_form_path(slug: submitter.slug)
find('#expand_form_button').click
draw_canvas
click_button 'Clear'
draw_canvas
click_button 'Sign and Complete'
expect(page).to have_content('Document has been signed!')
submitter.reload
expect(submitter.completed_at).to be_present
expect(field_value(submitter, 'Signature')).to be_present
end
end
describe 'resubmit flow' do
let(:template) do
create(:template, shared_link: true, account:, author:, only_field_types: %w[text])
end
before do
create(:account_config, account:, key: AccountConfig::ALLOW_TO_RESUBMIT, value: true)
end
pending 'allows a submitter to resubmit after completing' do
visit start_form_path(slug: template.slug)
fill_in 'Email', with: 'john@example.com'
click_button 'Start'
fill_in 'First Name', with: 'John'
find('#submit_form_button').click
expect(page).to have_content('Form has been completed!')
visit start_form_path(slug: template.slug)
fill_in 'Email', with: 'john@example.com'
click_button 'Start'
expect(page).not_to have_content('already completed')
fill_in 'First Name', with: 'John Updated'
find('#submit_form_button').click
expect(page).to have_content('Form has been completed!')
end
end
describe 'all optional fields' do
let(:template) { create(:template, account:, author:, only_field_types: %w[text date]) }
let(:submission) { create(:submission, template:) }
let(:submitter) do
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
end
before do
fields = template.fields.map { |f| f.dup.tap { |h| h['required'] = false } }
template.update!(fields:)
submission.update!(template_fields: fields)
end
pending 'completes with the header complete button without filling any fields' do
visit submit_form_path(slug: submitter.slug)
find('#expand_form_button').click
expect(page).to have_css('#complete_button_container')
page.execute_script('document.getElementById("complete_form_button")?.click() || document.querySelector(".complete-button")?.click()')
expect(page).to have_content('Form has been completed!')
submitter.reload
expect(submitter.completed_at).to be_present
end
end
describe 'decline with custom reason' do
let(:template) { create(:template, account:, author:, only_field_types: %w[text]) }
let(:submission) { create(:submission, template:) }
let(:submitter) do
create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:)
end
it 'records the decline reason on the submission event' do
visit submit_form_path(slug: submitter.slug)
find('#decline_button').click
fill_in 'reason', with: 'I do not agree with the terms and conditions'
within('dialog[open]') { click_button 'Decline' }
expect(page).to have_content('Form has been declined')
submitter.reload
expect(submitter.declined_at).to be_present
event = submission.submission_events.find_by(submitter:, event_type: 'decline_form')
expect(event).to be_present
expect(event.data).to include('reason' => 'I do not agree with the terms and conditions')
end
end
end

@ -0,0 +1,17 @@
# frozen_string_literal: true
RSpec.describe 'SMS Settings' do
let(:account) { create(:account) }
let(:user) { create(:user, account:) }
before do
sign_in(user)
end
it 'shows the SMS settings page with a placeholder in non-multitenant mode' do
visit settings_sms_path
expect(page).to have_content('SMS')
expect(page).to have_content(I18n.t('unlock_with_docuseal_pro'))
end
end

@ -0,0 +1,16 @@
# frozen_string_literal: true
RSpec.describe 'SSO Settings' do
let(:account) { create(:account) }
let(:user) { create(:user, account:) }
before do
sign_in(user)
end
it 'shows a placeholder in single-tenant mode' do
visit settings_sso_index_path
expect(page).to have_content(I18n.t('unlock_with_docuseal_pro'))
end
end

@ -0,0 +1,61 @@
# frozen_string_literal: true
RSpec.describe 'Submission Lifecycle' do
let(:account) { create(:account) }
let(:user) { create(:user, account:) }
before do
sign_in(user)
end
it 'shows pending submissions in the template page and completed after signing', sidekiq: :inline do
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
value: GenerateCertificate.call.transform_values(&:to_pem))
template = create(:template, account:, author: user, only_field_types: %w[text])
submission = create(:submission, template:)
submitter = create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:,
email: 'signer@example.com', name: nil)
visit template_path(template)
expect(page).to have_content('signer@example.com')
expect(page).to have_content('AWAITING')
visit submit_form_path(slug: submitter.slug)
fill_in 'First Name', with: 'Alice'
find('#submit_form_button').click
expect(page).to have_content('Form has been completed!')
visit template_path(template)
expect(page).to have_content('signer@example.com')
expect(page).to have_content('COMPLETED')
submitter.reload
expect(submitter.completed_at).to be_present
expect(submitter.ip).to eq('127.0.0.1')
end
it 'creates a submission via the send-to-recipients modal and shows it as pending' do
template = create(:template, account:, author: user, only_field_types: %w[text])
visit template_path(template)
click_link 'Send to Recipients', visible: :all
within('#modal') do
find('textarea[name="emails"]').set('recipient@example.com')
click_button 'Add Recipients'
end
expect(page).to have_content('recipient@example.com')
expect(page).to have_content('AWAITING')
submission = template.submissions.last
expect(submission).to be_present
expect(submission.submitters.first.email).to eq('recipient@example.com')
end
end

@ -0,0 +1,77 @@
# frozen_string_literal: true
RSpec.describe 'Template CRUD Edge Cases' do
let(:account) { create(:account) }
let(:user) { create(:user, account:) }
before do
sign_in(user)
end
describe 'restoring an archived template' do
let!(:template) { create(:template, account:, author: user, archived_at: Time.current,
except_field_types: %w[phone payment]) }
it 'restores an archived template from the template page' do
visit template_path(template)
expect(page).to have_content('Archived')
page.find('form[action*="restore"]').click
expect(template.reload.archived_at).to be_nil
end
pending 'lists archived templates on the dedicated index page' do
visit templates_archived_index_path
expect(page).to have_content(template.name)
find('button[aria-label]').click
expect(page).not_to have_content(template.name)
end
end
describe 'template share link' do
let!(:template) { create(:template, account:, author: user, except_field_types: %w[phone payment]) }
it 'opens the share link modal' do
visit template_path(template)
click_link 'Link'
expect(page).to have_field('embedding_url', with: /#{template.slug}/)
end
it 'enables and disables the share link' do
visit template_share_link_path(template)
check 'template_shared_link'
page.execute_script('document.getElementById("shared_link_form").submit()')
expect(template.reload.shared_link).to be true
visit template_share_link_path(template)
uncheck 'template_shared_link'
page.execute_script('document.getElementById("shared_link_form").submit()')
expect(template.reload.shared_link).to be false
end
end
describe 'template folder management' do
it 'navigates through folders on the dashboard' do
folder = create(:template_folder, account:, author: user, name: 'My Folder')
template_in_folder = create(:template, account:, author: user, folder:,
except_field_types: %w[phone payment])
template_root = create(:template, account:, author: user, name: 'Root Template',
except_field_types: %w[phone payment])
visit folder_path(folder)
expect(page).to have_content(folder.name)
expect(page).to have_content(template_in_folder.name)
expect(page).not_to have_content(template_root.name)
end
end
end
Loading…
Cancel
Save