Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion endpoints/send_feedback_mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function sendFeedbackMail(
string $feedbackQuestion6,
string $feedbackQuestion7
): void {
global $smtpHost, $smtpPort, $smtpUser, $smtpPassword, $smtpSender, $feedbackAddress, $smtpSecure, $smtpAuth, $showMslLabs, $showGGMsProperties;
global $smtpHost, $smtpPort, $smtpUser, $smtpPassword, $smtpSender, $feedbackAddress, $smtpSecure, $smtpAuth, $showMslLabs, $showGGMsProperties, $SIMULATE_EMAIL;

// Determine ELMO version
if ($showMslLabs) {
Expand All @@ -59,6 +59,10 @@ function sendFeedbackMail(
}
// Testing log
error_log("ELMO Version for Feedback: {$elmoVersion}");
if ($SIMULATE_EMAIL ?? false) {
error_log("Simulating the sending of feedback email due to SIMULATE_EMAIL being set.");
return;
}
// Network test before sending
if (!testGfzSmtpConnectivity()) {
echo json_encode(['success' => false, 'message' => 'Sorry, we had an issue connecting to the Email SMTP server. You can reach out to us directly per email.']);
Expand Down
9 changes: 9 additions & 0 deletions js/eventhandlers/formgroups/feedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,18 @@ $(document).ready(function () {
return $elements;
}

function allFieldsMissing(form) {
return [...form.querySelectorAll('textarea[name^="feedbackQuestion"]')]
.every((field) => field.value.trim() === '');
}

sendButton.click(async function (event) {
event.preventDefault();

if (allFieldsMissing(feedbackForm[0])) {
return;
}

sendButton
.prop("disabled", true)
.attr("aria-busy", "true")
Expand Down
21 changes: 7 additions & 14 deletions modals.html
Original file line number Diff line number Diff line change
Expand Up @@ -441,44 +441,37 @@ <h2 class="modal-title fs-5" id="label-feedbackmodal" data-translate="modals.fee
<div class="mb-3">
<label for="input-feedback-question1" class="form-label"
data-translate="modals.feedback.question1">Which functions of the new metadata editor do you find particularly helpful?</label>
<textarea class="form-control" id="input-feedback-question1" name="feedbackQuestion1" rows="3"
required></textarea>
<textarea class="form-control" id="input-feedback-question1" name="feedbackQuestion1" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="input-feedback-question2" class="form-label"
data-translate="modals.feedback.question2">Is there a particular design or user interface change that you like?</label>
<textarea class="form-control" id="input-feedback-question2" name="feedbackQuestion2" rows="3"
required></textarea>
<textarea class="form-control" id="input-feedback-question2" name="feedbackQuestion2" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="input-feedback-question3" class="form-label"
data-translate="modals.feedback.question3">What do you find positive about the usability of the new editor?</label>
<textarea class="form-control" id="input-feedback-question3" name="feedbackQuestion3" rows="3"
required></textarea>
<textarea class="form-control" id="input-feedback-question3" name="feedbackQuestion3" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="input-feedback-question4" class="form-label"
data-translate="modals.feedback.question4">Which functions of the new editor do you find difficult to use?</label>
<textarea class="form-control" id="input-feedback-question4" name="feedbackQuestion4" rows="3"
required></textarea>
<textarea class="form-control" id="input-feedback-question4" name="feedbackQuestion4" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="input-feedback-question5" class="form-label"
data-translate="modals.feedback.question5">Are there any aspects of the user interface that you find confusing or annoying?</label>
<textarea class="form-control" id="input-feedback-question5" name="feedbackQuestion5" rows="3"
required></textarea>
<textarea class="form-control" id="input-feedback-question5" name="feedbackQuestion5" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="input-feedback-question6" class="form-label"
data-translate="modals.feedback.question6">Do you miss certain functions in the new metadata editor?</label>
<textarea class="form-control" id="input-feedback-question6" name="feedbackQuestion6" rows="3"
required></textarea>
<textarea class="form-control" id="input-feedback-question6" name="feedbackQuestion6" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="input-feedback-question7" class="form-label"
data-translate="modals.feedback.question7">Is there a specific improvement you would like to see?</label>
<textarea class="form-control" id="input-feedback-question7" name="feedbackQuestion7" rows="3"
required></textarea>
<textarea class="form-control" id="input-feedback-question7" name="feedbackQuestion7" rows="3"></textarea>
</div>
<button class="btn btn-primary feedback-button-send" type="submit" id="button-feedback-send"
data-translate="modals.feedback.sendButton">Send feedback</button>
Expand Down
54 changes: 54 additions & 0 deletions tests/playwright/features/feedback-modal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,60 @@ async function mockFeedbackEndpoint(
}

test.describe('Feedback modal interactions', () => {
test('does not call the backend when every feedback field is empty', async ({ page }) => {
const { feedbackModal } = await navigateToFeedbackModal(page);
const sendButton = feedbackModal.locator('#button-feedback-send');
let feedbackPosted = false;

await page.route(FEEDBACK_ENDPOINT, async (route) => {
feedbackPosted = true;
await route.fulfill({
status: 200,
contentType: 'text/plain',
body: 'OK',
});
});

async function clickSendAndWatchForPost() {
const feedbackRequest = page
.waitForRequest((request) => request.url().includes('send_feedback_mail.php'), { timeout: 1500 })
.catch(() => null);

await sendButton.click();
return feedbackRequest;
}

expect(await clickSendAndWatchForPost()).toBeNull();
await expect(feedbackModal.locator('#form-feedback')).toBeVisible();
await expect(sendButton).toBeEnabled();
expect(feedbackPosted).toBe(false);

const fieldState = await feedbackModal.locator('#form-feedback').evaluate((form) => {
const fields = [...form.querySelectorAll('textarea[name^="feedbackQuestion"]')];
return {
required: fields.filter((field) => field.required).map((field) => field.id),
invalid: fields.filter((field) => field.classList.contains('is-invalid') || field.getAttribute('aria-invalid') === 'true').map((field) => field.id),
wasValidated: form.classList.contains('was-validated'),
};
});
expect(fieldState.required).toEqual([]);
expect(fieldState.invalid).toEqual([]);
expect(fieldState.wasValidated).toBe(false);

await feedbackModal.locator('textarea[name="feedbackQuestion3"]').fill(' ');
expect(await clickSendAndWatchForPost()).toBeNull();
expect(feedbackPosted).toBe(false);

await feedbackModal.locator('textarea[name="feedbackQuestion3"]').fill('One answer');
const filledRequest = page.waitForRequest((request) =>
request.url().includes('send_feedback_mail.php')
);
await sendButton.click();
expect((await filledRequest)?.url()).toContain('send_feedback_mail.php');

await page.unroute(FEEDBACK_ENDPOINT);
});

test('shows success feedback flow when the backend responds with 200', async ({ page }) => {
const { feedbackButton, feedbackModal } = await navigateToFeedbackModal(page);
const feedbackForm = feedbackModal.locator('#form-feedback');
Expand Down
Loading