diff --git a/.devcontainer/default/devcontainer.json b/.devcontainer/default/devcontainer.json index 5a38a05d..58fb5952 100644 --- a/.devcontainer/default/devcontainer.json +++ b/.devcontainer/default/devcontainer.json @@ -1,31 +1,30 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile { - "name": "ubuntu16.04", - "build": { - // Sets the run context to one level up instead of the .devcontainer folder. - "context": "../..", - // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. - "dockerfile": "../../Dockerfile" - }, - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8000], - - // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "cat /etc/os-release", - - // Configure tool-specific properties. - // "customizations": {}, - - "extensions": [ - "ms-python.python@2021.9.1246542782", - "ms-python.debugpy@2023.1.12492010" - ] - - // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "devcontainer" -} + "name": "ubuntu16.04", + "build": { + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "../..", + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerfile": "../../Dockerfile" + }, + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ + 8000 + ], + // Uncomment the next line to run commands after the container is created. + "postCreateCommand": "cat /etc/os-release", + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python@2021.9.1246542782", + "ms-python.debugpy@2023.1.12492010" + ] + } + } + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer" +} \ No newline at end of file diff --git a/.devcontainer/ubuntu24.04/devcontainer.json b/.devcontainer/ubuntu24.04/devcontainer.json index 20276fd6..03447c03 100644 --- a/.devcontainer/ubuntu24.04/devcontainer.json +++ b/.devcontainer/ubuntu24.04/devcontainer.json @@ -8,19 +8,23 @@ // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. "dockerfile": "../../Dockerfile.ubuntu24.04" }, - // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8000], - + "forwardPorts": [ + 8000 + ], // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "cat /etc/os-release" - + "postCreateCommand": "cat /etc/os-release", // Configure tool-specific properties. - // "customizations": {}, - + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python@2021.9.1246542782", + "ms-python.debugpy@2023.1.12492010" + ] + } + } // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "devcontainer" -} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 08c0e80f..0943b0c6 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,12 @@ venv/ # Salt deploy/pillar/eaa.sls* + +donation_portal_env + +# Frontend +frontend/node_modules/ +frontend/test-results/ +frontend/playwright-report/ +frontend/blob-report/ +frontend/playwright/.cache/ diff --git a/.pyup.yml b/.pyup.yml deleted file mode 100644 index 6b9acfa3..00000000 --- a/.pyup.yml +++ /dev/null @@ -1,42 +0,0 @@ -# configure updates globally, default is all -update: security # allowed [all, insecure, False] - -# configure dependency pinning globally, default is True -pin: True # allowed [True, False] - -# set the default branch, default is the default branch on GitHub -branch: master - -# update schedule, default is not set -# the bot will visit the repo once and bundle all updates in a single PR for the given -# day/week/month -# schedule: "every day" # allowed ["every day", "every week", "every two weeks", "every month"] - -# configure requirement files search, default is True -# the bot will search for files ending with .txt/.pip with a requirements in their file paths -search: False # allowed [True, False] - -# adding requirements files manually, default is empty -requirements: - - deps/pip: - updates: security - pin: True - -# add a label to pull requests, default is not set -# requires private repo permissions, even on public repos -# label_prs: update - -# assign users to pull requests, default is not set -# requires private repo permissions, even on public repos -# assignees: -# - carl -# - carlsen - -# configure the branch prefix the bot is using, default is pyup- -# branch_prefix: pyup/ - -# set a global prefix for PRs, default is empty -# pr_prefix: "Bug #12345" - -# allow to close stale PRs, default is True -# close_prs: True diff --git a/.vscode/settings.json b/.vscode/settings.json index 68a0e534..9c3ed2e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "python.pythonPath": "/workspaces/donation_portal-2/donation_portal_env/bin/python2.7" + "python.pythonPath": "/workspaces/donation_portal-2/donation_portal_env/bin/python2.7", + "python.linting.enabled": true } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3bd3e9b1..363c8194 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # For local development and testing only. -FROM ubuntu:16.04 +FROM --platform=linux/amd64 ubuntu:16.04 # Install sudo RUN apt-get update && apt-get install -y sudo @@ -39,7 +39,9 @@ RUN sudo apt-get update && sudo apt-get install -y \ USER postgres RUN service postgresql start && \ psql -c "ALTER USER postgres PASSWORD 'password';" && \ - psql -c "CREATE DATABASE eaa;" + psql -c "CREATE ROLE eaa WITH LOGIN PASSWORD 'password';" && \ + psql -c "CREATE DATABASE donations;" && \ + psql -c "GRANT ALL PRIVILEGES ON DATABASE donations TO eaa;" USER devuser # Upgrade pip to the last version that supports Python 2.7 and install the last @@ -65,6 +67,9 @@ RUN mkdir $NVM_DIR && \ # pip install -r deps/pip.base && \ # pip install -r deps/pip +# Fix "missing locales" errors in this Ubuntu image +RUN sudo locale-gen "en_US.UTF-8" + # Document that the service listens on port 8000. Note, this doesn't actually # open the port - you'll need to run `docker run -p 8000:8000 `. EXPOSE 8000 \ No newline at end of file diff --git a/Dockerfile.ubuntu24.04 b/Dockerfile.ubuntu24.04 index c5f6b9d2..99e59294 100644 --- a/Dockerfile.ubuntu24.04 +++ b/Dockerfile.ubuntu24.04 @@ -60,7 +60,9 @@ RUN pyenv global 2.7.18 USER postgres RUN service postgresql start && \ psql -c "ALTER USER postgres PASSWORD 'password';" && \ - psql -c "CREATE DATABASE eaa;" + psql -c "CREATE ROLE eaa WITH LOGIN PASSWORD 'password';" && \ + psql -c "CREATE DATABASE donations;" && \ + psql -c "GRANT ALL PRIVILEGES ON DATABASE donations TO eaa;" USER devuser # Upgrade pip to the last version that supports Python 2.7 and install the last diff --git a/donation/views/pledge.py b/donation/views/pledge.py index f40d7b2c..85b53c1a 100644 --- a/donation/views/pledge.py +++ b/donation/views/pledge.py @@ -195,6 +195,12 @@ def process_session_completed(data): @app.task() def process_payment_intent_succeeded(data, org): print(org) + + # Ignore duplicate events (Stripe sometimes sends the same event multiple times). + # See here for more: https://docs.stripe.com/webhooks#handle-duplicate-events + if StripeTransaction.objects.filter(payment_intent_id=data['id']).exists(): + return + stripe.api_key = settings.STRIPE_API_KEY_DICT.get(org) if org == "eaa": diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..03b6164a --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,52 @@ +# A simple dependency free donation form +This form is designed to replace our react-based donation form. The goals of this form are to be: + +**Dependency free** - no need to regularly update dependencies. +**Easy to deploy** - just copy and paste the HTML into the WordPress site. +**Visually improved** - and matches the design of the rest of our WordPress site. +**Accessible** - follows best practices for accessibility. +**Easy to customise** - just clone this repo and change the HTML/CSS/JS as needed. +**Easy to maintain** - only a few hundred lines of simple code that beginners can grok. + +## Allocation terminology +| Term | Description | +|--------|---------------| +| Most effective (default) | This option allocates the donation to the most effective charities (according to us), based on evidence and need. This option is selected by default in the standard form. | +| Specific charity (or charities) | This option allows donors to allocate their donation to our various partner charities. This option can be selected in the standard form. | +| Direct link charity | A direct link charity is an EAA partner charity selected by visiting our `/donate` url with `?charity=charity_name` appended to the end. Direct link charities are not relevant to the standard form. | + +## Compatibility +We try to only use features that are "Widely available" according to [Baseline](https://web.dev/baseline) to ensure compatibility with all modern browsers. + +## Requirements +- NodeJS version v22.9.01 (this only used to concat the files and for development convenience. We will replace all need for NodeJS with Python once the rest of the project has moved to Python 3). + +## Deployment +__Note: the deployment process has changed to allow us to avoid iframes and to move beyond NodeJS version 8 (the latest version available for our current server). To deploy, do the following on your *local* machine:__ + +1. Run `npm run build` to concatenate the src files into a single file that will be located at `donation/templates/donation_form.html`. +2. Copy the section between the relevant comments in `donation/templates/donation_form.html` into the WordPress site. + +## Development + +### Without Django (recommended) +If you're just working on the frontend, you can avoid setting up Django. +1. Run `npx http-server -p 8000 ../donation/templates/` from the `frontend` directory to serve the files. +2. Run `npm run dev` from the `frontend` directory to watch files for changes and rebuild the concatenated file at `donation/templates/donation_form.html`. +3. Visit the page at `localhost:8000/donation_form.html` to see the form. + +### With Django +1. Follow the instructions to setup and run the Django server (`python manage.py runserver`). +2. Run `npm run dev` from the `frontend` directory to watch files for changes and rebuild the concatenated file at `donation/templates/donation_form.html`. +3. Visit the page at `localhost:8000/pledge_new` to see the form. + +## Testing + +### With VSCode Playwright extension (recommended) +1. Run `npm install` and then `npx playwright install` to install the necessary dependencies. +2. Install the Playwright VSCode extension. This is particularly useful for creating new tests as it lets you record your actions in the browser. +3. Run the tests from VSCode's "Testing" sidebar. + +### Commandline +1. Run `npm install` and then `npx playwright install` to install the necessary dependencies. +2. Run `npx playwright test` to run the tests from the command line. \ No newline at end of file diff --git a/frontend/build.js b/frontend/build.js new file mode 100644 index 00000000..63b4dc9e --- /dev/null +++ b/frontend/build.js @@ -0,0 +1,80 @@ +const fs = require("fs"); + +// Note: The "Paste this into WordPress" instructions below are for the final +// output. Do not copy and paste any of the code in this file into WordPress. +const donationFormHtml = ` + + + + + + EAA Donation Form + + + + + + + + + + + +
+ + + + + + + +`; + +fs.writeFileSync( + "../donation/templates/donation_form.html", + donationFormHtml, + "utf8" +); + +console.log("Build finished!"); diff --git a/frontend/dev.env b/frontend/dev.env new file mode 100644 index 00000000..dfd1478f --- /dev/null +++ b/frontend/dev.env @@ -0,0 +1,4 @@ +# DEV VARIABLES +STRIPE_API_KEY_EAA=pk_test_51I1Q7kEO8N9VNJdmyMo0YEudkFMpRHZrkC4mstwlONj5kR81SRzebGlCQbJBSk6d5qT6QUObEqV9Q3tjPQiFmGZH00wvKu7GSr +STRIPE_API_KEY_EAAE=pk_test_51MbDLyBiXhYHr2MDkG96cSLklsnxVpXvYQAqFoAoPIxca0fG0LO4AGozaA7mGdKg5zzpddRy611y7fzU6cMo881K00ZCan1Kuj +ORIGIN=http://localhost:8000 \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..3362e25a --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "eaa-donation-form", + "version": "1.0.0", + "description": "A simple dependency free donation form", + "engines": { + "node": ">=22.4.1" + }, + "main": "build.js", + "scripts": { + "build": "node --env-file=prod.env build.js", + "dev": "node --env-file=dev.env --watch-path=./src build.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Nathan Sherburn", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.48.0", + "@types/node": "^22.7.4" + } +} diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 00000000..a05d8b5a --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/frontend/prod.env b/frontend/prod.env new file mode 100644 index 00000000..0b190188 --- /dev/null +++ b/frontend/prod.env @@ -0,0 +1,4 @@ +# PROD VARIABLES +STRIPE_API_KEY_EAA=pk_live_51I1Q7kEO8N9VNJdmgqN19KmuB7haiTg9A9bHfEkHlS7cxlPk0A5ejkHlWuq2sAVvE7QWQXYnTQhRbtEXAT9dSx7A00fl6fhFl5 +STRIPE_API_KEY_EAAE=pk_live_51MbDLyBiXhYHr2MDTZOSex4Vvu3SQJNL4OylN4m9eg1dNXJLFOEASUUiYWASR3t075ewmrCYhqQAonqJlCv4lFVE00FgbQz9UA +ORIGIN=https://donations.effectivealtruism.org.au \ No newline at end of file diff --git a/frontend/src/allocation-section.html b/frontend/src/allocation-section.html new file mode 100644 index 00000000..37690b53 --- /dev/null +++ b/frontend/src/allocation-section.html @@ -0,0 +1,55 @@ + + +
+

I would like my gift to go to

+
+ + + + + +
+

+ ^According to EAA. We select the most effective charities each quarter + based on evidence and need. You can read more about how we evaluate + charities + here. +

+
+ + \ No newline at end of file diff --git a/frontend/src/amount-section.html b/frontend/src/amount-section.html new file mode 100644 index 00000000..8f71ef00 --- /dev/null +++ b/frontend/src/amount-section.html @@ -0,0 +1,144 @@ + + +
+

I would like to gift

+
+ + + + + + + + +
+
+ + + $ + + +
+
+ + diff --git a/frontend/src/amplify-impact-section.html b/frontend/src/amplify-impact-section.html new file mode 100644 index 00000000..64ac9071 --- /dev/null +++ b/frontend/src/amplify-impact-section.html @@ -0,0 +1,211 @@ + + +
+

Amplify my impact

+

I would like to add:

+
+ + + + + + + + + + + + + + +
+
+ + + $ + + +
+

+ To grow Effective Altruism Australia's work + +

+
+ EA Australia is on a mission to help Australians tackle the world’s most + pressing problems. We have big plans to reach more people, improve our + infrastructure, and grow our community. With your support, we can move more + donations to high impact causes. +
+
+ + diff --git a/frontend/src/bank-instructions-section.html b/frontend/src/bank-instructions-section.html new file mode 100644 index 00000000..da1ab151 --- /dev/null +++ b/frontend/src/bank-instructions-section.html @@ -0,0 +1,128 @@ + + +
+

+ Thank you, ! +

+

+ Your donation will be allocated to + . +

+
+
+

What to do next?

+

+ Please make sure that you complete the process by + $ + to: +

+

+ Account Name
+ Effective Altruism Australia
+ (Don't worry if it doesn't fit) +

+

+ BSB
+ 083 170 +

+ +

+ Account No
+ 306 556 167 +

+

+ Reference Number
+
+ (Place this in the transaction description) +

+
+
+

+ Receipt +

+

+ We will send you a tax deductible receipt + once we have confirmed the bank transfer. +

+

+ Any questions? +

+

+ Please email us at info@eaa.org.au or + call us on +61 492 841 596, if you have any + questions. +

+

+ We have also emailed you these instructions – please check your spam folder + if you have not received them. +

+

+ Best wishes and thanks,

+ The Effective Altruism Australia team +

+
+ + \ No newline at end of file diff --git a/frontend/src/communications-section.html b/frontend/src/communications-section.html new file mode 100644 index 00000000..706406b8 --- /dev/null +++ b/frontend/src/communications-section.html @@ -0,0 +1,107 @@ + + +
+
+

How did you hear about us?

+ +
+ +
+ + + + + +
+
+ + diff --git a/frontend/src/direct-link-charity-section.html b/frontend/src/direct-link-charity-section.html new file mode 100644 index 00000000..60536bc7 --- /dev/null +++ b/frontend/src/direct-link-charity-section.html @@ -0,0 +1,35 @@ + + + + + diff --git a/frontend/src/donate-button-section.html b/frontend/src/donate-button-section.html new file mode 100644 index 00000000..016621a7 --- /dev/null +++ b/frontend/src/donate-button-section.html @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/donation-frequency-section.html b/frontend/src/donation-frequency-section.html new file mode 100644 index 00000000..db8eb09b --- /dev/null +++ b/frontend/src/donation-frequency-section.html @@ -0,0 +1,42 @@ + + +
+

I would like to give

+ +
+ + + + +
+
+ + \ No newline at end of file diff --git a/frontend/src/error-section.html b/frontend/src/error-section.html new file mode 100644 index 00000000..692851bc --- /dev/null +++ b/frontend/src/error-section.html @@ -0,0 +1,19 @@ + + +
+

Something's gone wrong

+

Please let us know about this issue at info@eaa.org.au

+

Click here to start again.

+
+ + \ No newline at end of file diff --git a/frontend/src/festive-gift-section.html b/frontend/src/festive-gift-section.html new file mode 100644 index 00000000..44d0e316 --- /dev/null +++ b/frontend/src/festive-gift-section.html @@ -0,0 +1,240 @@ + + + + + diff --git a/frontend/src/gift-section.html b/frontend/src/gift-section.html new file mode 100644 index 00000000..a6623bb4 --- /dev/null +++ b/frontend/src/gift-section.html @@ -0,0 +1,84 @@ + + +
+
+

+ Are you making this donation as a gift to someone? +

+ + + +
+
+ + diff --git a/frontend/src/loader.html b/frontend/src/loader.html new file mode 100644 index 00000000..f9e58fa3 --- /dev/null +++ b/frontend/src/loader.html @@ -0,0 +1,34 @@ + + +
+
+
diff --git a/frontend/src/payment-method-section.html b/frontend/src/payment-method-section.html new file mode 100644 index 00000000..da50895b --- /dev/null +++ b/frontend/src/payment-method-section.html @@ -0,0 +1,59 @@ + + +
+

Payment Method

+ +
+ + + + + +
+ + +
+ + diff --git a/frontend/src/personal-details-section.html b/frontend/src/personal-details-section.html new file mode 100644 index 00000000..c2ff692a --- /dev/null +++ b/frontend/src/personal-details-section.html @@ -0,0 +1,369 @@ + + + diff --git a/frontend/src/scripts/FormController.js b/frontend/src/scripts/FormController.js new file mode 100644 index 00000000..125f39dc --- /dev/null +++ b/frontend/src/scripts/FormController.js @@ -0,0 +1,394 @@ +// We're using Shadow DOM to isolate this donation form's design from WordPress's CSS +const host = document.querySelector("#donation-form-host"); +const shadow = host.attachShadow({ mode: "open" }); +const template = document.getElementById("donation-form"); +shadow.appendChild(template.content); + +class FormController { + #allocationType = "default"; // default, specific, direct-link + #directLinkCharity; + #directLinkCharityDetails; + #donationFrequency = "one-time"; + #donorCountry; + #donorEmail; + #donorFirstName; + #donorLastName; + #donorPostcode; + #isFestiveGift = false; + #partnerCharities = []; + #paymentMethod = "credit-card"; + #showThankYouMessage; + #subscribeToCommunity = false; + #subscribeToNewsletter = false; + #subscribeToUpdates = true; + #tipSize = 10; + #tipType = "percentage"; + #tipDollarAmount = 0; + #stripe; + #howDidYouHearAboutUs = ""; + #basicDonationAmount = 0; + #specificAllocations = {}; + + constructor() { + // Get url parameters + const urlParams = new URLSearchParams(window.location.search); + this.#showThankYouMessage = urlParams.get("thankyou") === ""; + this.#directLinkCharity = urlParams.get("charity"); + + // Initialise Stripe + if (this.#directLinkCharity === "eaae") { + this.#stripe = window.Stripe(STRIPE_API_KEY_EAAE); + } else { + this.#stripe = window.Stripe(STRIPE_API_KEY_EAA); + } + + // Decide which form to build based on the url params + if (this.#showThankYouMessage) { + new ThankyouSection(); + } else if (this.#directLinkCharity === "eaae") { + this.#renderEaaeForm(); + } else if (this.#directLinkCharity) { + this.#allocationType = "direct-link"; + this.#renderDirectLinkCharityForm(); + } else { + this.#renderStandardForm(); + } + } + + #renderEaaeForm() { + $("form").innerText = "EAAE Not yet implemented"; + } + + async #renderDirectLinkCharityForm() { + new AmountSection(); + new AmplifyImpactSection(); + new TotalAmountSection(); + new PersonalDetailsSection(); + new CommunicationsSection(); + new PaymentMethodSection(); + new FestiveGiftSection(); + new DonateButtonSection(); + new DonationFrequencySection(); + + const response = await fetch(ORIGIN + "/partner_charities"); + this.#partnerCharities = await response.json(); + this.#partnerCharities.forEach((charity) => { + this.#specificAllocations[charity.slug_id] = 0; + }); + this.#directLinkCharityDetails = this.#partnerCharities.find( + (charity) => charity.slug_id === this.#directLinkCharity + ); + if (!this.#directLinkCharityDetails) { + new ErrorSection(); + return; + } + new DirectLinkCharitySection(this.#directLinkCharityDetails); + } + + async #renderStandardForm() { + new DonationFrequencySection(); + new AllocationSection(); + new AmountSection(); + new AmplifyImpactSection(); + new TotalAmountSection(); + new PersonalDetailsSection(); + new CommunicationsSection(); + new PaymentMethodSection(); + new FestiveGiftSection(); + new DonateButtonSection(); + + const response = await fetch(ORIGIN + "/partner_charities"); + this.#partnerCharities = await response.json(); + this.#partnerCharities = this.#partnerCharities.filter( + (charity) => charity.slug_id !== "eaa-amplify" + ); + this.#partnerCharities.forEach((charity) => { + this.#specificAllocations[charity.slug_id] = 0; + }); + + new SpecificAllocationSection(this.#partnerCharities); + } + + setDonationFrequency(frequency) { + this.#donationFrequency = frequency; + this.updateTotalAmountSection(); + } + + getDonationFrequency() { + return this.#donationFrequency; + } + + setAllocationType(type) { + this.#allocationType = type; + if (this.#allocationType === "specific") { + AmountSection.hide(); + SpecificAllocationSection.show(); + } else { + SpecificAllocationSection.hide(); + AmountSection.show(); + } + this.updateTipDollarAmount(); + this.updateTotalAmountSection(); + } + + setBasicDonationAmount(amount) { + this.#basicDonationAmount = +amount; + this.updateTipDollarAmount(); + this.updateTotalAmountSection(); + } + + setCharityAllocation(charity, amount) { + this.#specificAllocations[charity] = amount; + this.updateTipDollarAmount(); + this.updateTotalAmountSection(); + } + + setTipValues(amount, type) { + this.#tipType = type; + this.#tipSize = +amount; + this.updateTipDollarAmount(); + this.updateTotalAmountSection(); + } + + #getSpecificAllocationsTotal() { + let total = 0; + Object.values(this.#specificAllocations).forEach((amount) => { + total += +amount; + }); + return total; + } + + updateTipDollarAmount() { + const totalDonationAmount = + this.#allocationType === "specific" + ? this.#getSpecificAllocationsTotal() + : this.#basicDonationAmount; + if (this.#tipType === "percentage") { + this.#tipDollarAmount = +totalDonationAmount * (this.#tipSize / 100); + } else if (this.#tipType === "dollar") { + this.#tipDollarAmount = this.#tipSize; + } + } + + updateTotalAmountSection() { + TotalAmountSection.render({ + allocationType: this.#allocationType, + basicDonationAmount: this.#basicDonationAmount, + specificAllocations: this.#specificAllocations, + tipType: this.#tipType, + tipSize: this.#tipSize, + tipDollarAmount: this.#tipDollarAmount, + }); + } + + setPaymentMethod(method) { + this.#paymentMethod = method; + } + + getPartnerCharities() { + return this.#partnerCharities; + } + + setIsFestiveGift(isFestiveGift) { + this.#isFestiveGift = isFestiveGift; + } + + setDonorFirstName(firstName) { + this.#donorFirstName = firstName; + } + + setDonorLastName(lastName) { + this.#donorLastName = lastName; + } + + setDonorEmail(email) { + this.#donorEmail = email; + } + + setDonorPostcode(postcode) { + this.#donorPostcode = postcode; + } + + setDonorCountry(country) { + this.#donorCountry = country; + } + + setSubscribeToUpdates(subscribe) { + this.#subscribeToUpdates = subscribe; + } + + setSubscribeToNewsletter(subscribe) { + this.#subscribeToNewsletter = subscribe; + } + + setSubscribeToCommunity(subscribe) { + this.#subscribeToCommunity = subscribe; + } + + setHowDidYouHearAboutUs(howDidYouHearAboutUs) { + this.#howDidYouHearAboutUs = howDidYouHearAboutUs; + } + + handleFormSubmit() { + if ( + this.#allocationType === "specific" && + this.#getSpecificAllocationsTotal() + this.#tipDollarAmount < 2 + ) { + alert("Please allocate at least $2 across your preferred charities."); + $("#allocation-section--specific-allocation").focus(); + return false; + } + + if ( + (this.#allocationType === "direct-link" || + this.#allocationType === "default") && + this.#basicDonationAmount + this.#tipDollarAmount < 2 + ) { + alert("Please select an amount of at least $2."); + $("#amount-section--custom-amount-input").focus(); + return false; + } + + $("#loader").style.display = "block"; + + let formData = this.#buildFormData(); + fetch(ORIGIN + "/pledge_new/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }) + .then((response) => response.json()) + .then(async (data) => { + const result = await FestiveGiftSection.generateAndSendCard({ + paymentReference: data, + isFestiveGift: this.#isFestiveGift, + donorName: this.#donorFirstName, + donorEmail: this.#donorEmail, + allocationType: this.#allocationType, + basicDonationAmount: this.#basicDonationAmount, + specificAllocations: this.#specificAllocations, + directLinkCharity: this.#directLinkCharity, + }); + $("#loader").style.display = "none"; + if (result === "error") { + return; + } else { + this.#handleFormSubmitResponse(data, formData); + } + }); + return false; + } + + #handleFormSubmitResponse(data, formData) { + if (data.bank_reference) { + this.#renderBankTransferInstructions(formData, data); + } else if (data.id) { + this.#stripe.redirectToCheckout({ sessionId: data.id }); + } else { + $("form").innerText = + "Error submitting form. Please try again. If this problem persists, please email info@eaa.org.au."; + } + } + + #renderBankTransferInstructions(formData, data) { + new BankInstructionsSection({ + allocationType: this.#allocationType, + firstName: formData.first_name, + bankReference: data.bank_reference, + recurring: formData.recurring, + basicDonationAmount: this.#basicDonationAmount, + tipDollarAmount: this.#tipDollarAmount, + specificAllocationsTotal: this.#getSpecificAllocationsTotal(), + directLinkCharityDetails: this.#directLinkCharityDetails, + }); + hide("#festive-gift-section"); + hide("#payment-method-section"); + hide("#specific-allocation-section"); + hide("#amplify-impact-section"); + hide("#total-amount-section"); + hide("#communications-section"); + hide("#personal-details-section"); + hide("#amount-section"); + hide("#allocation-section"); + hide("#donation-frequency-section"); + hide("#direct-link-charity-section"); + hide("#festive-gift-section"); + hide("#thank-you-message"); + hide("#donate-button-section"); + } + + #buildFormData() { + let formData = { + payment_method: this.#paymentMethod, + recurring_frequency: this.#donationFrequency, + recurring: this.#donationFrequency === "monthly" ? true : false, + first_name: this.#donorFirstName, + last_name: this.#donorLastName, + email: this.#donorEmail, + subscribe_to_updates: this.#subscribeToUpdates, + subscribe_to_newsletter: this.#subscribeToNewsletter, + connect_to_community: this.#subscribeToCommunity, + how_did_you_hear_about_us_db: this.#howDidYouHearAboutUs, + postcode: this.#donorPostcode, + country: this.#donorCountry, + }; + + if (this.#allocationType === "specific") { + formData = this.#addSpecificAllocationFormData(formData); + } else { + formData = this.#addStandardAllocationFormData(formData); + } + return formData; + } + + #addStandardAllocationFormData(formData) { + let totalForms = 0; + if (this.#basicDonationAmount > 0) { + formData[`form-${totalForms}-id`] = null; + formData[`form-${totalForms}-amount`] = + this.#basicDonationAmount.toString(); + formData[`form-${totalForms}-partner_charity`] = + this.#directLinkCharity || "unallocated"; + totalForms++; + } + if (this.#tipDollarAmount > 0) { + formData[`form-${totalForms}-id`] = null; + formData[`form-${totalForms}-amount`] = this.#tipDollarAmount + .toFixed(2) + .toString(); + formData[`form-${totalForms}-partner_charity`] = "eaa-amplify"; + totalForms++; + } + formData["form-TOTAL_FORMS"] = totalForms; + formData["form-INITIAL_FORMS"] = totalForms; + return formData; + } + + #addSpecificAllocationFormData(formData) { + let totalForms = 0; + this.#partnerCharities.forEach((charity) => { + let amount = $(`#${charity.slug_id}-amount`).value; + if (amount > 0) { + formData[`form-${totalForms}-id`] = null; + formData[`form-${totalForms}-amount`] = amount; + formData[`form-${totalForms}-partner_charity`] = charity.slug_id; + totalForms++; + } + }); + if (this.#tipDollarAmount > 0) { + formData[`form-${totalForms}-id`] = null; + formData[`form-${totalForms}-amount`] = this.#tipDollarAmount + .toFixed(2) + .toString(); + formData[`form-${totalForms}-partner_charity`] = "eaa-amplify"; + totalForms++; + } + formData["form-TOTAL_FORMS"] = totalForms; + formData["form-INITIAL_FORMS"] = totalForms; + return formData; + } +} + +const formController = new FormController(); diff --git a/frontend/src/scripts/utilities.js b/frontend/src/scripts/utilities.js new file mode 100644 index 00000000..0a7fe3ad --- /dev/null +++ b/frontend/src/scripts/utilities.js @@ -0,0 +1,23 @@ +// Helper functions +function $(id) { + return host.shadowRoot.querySelector(id); +} + +function $$(id) { + return host.shadowRoot.querySelectorAll(id); +} + +function hide(id) { + $(id).style.display = "none"; + $(id).style.opacity = 0; +} + +function showBlock(id) { + $(id).style.display = "block"; + $(id).style.opacity = 1; +} + +function showFlex(id) { + $(id).style.display = "flex"; + $(id).style.opacity = 1; +} \ No newline at end of file diff --git a/frontend/src/specific-allocation-section.html b/frontend/src/specific-allocation-section.html new file mode 100644 index 00000000..ef543987 --- /dev/null +++ b/frontend/src/specific-allocation-section.html @@ -0,0 +1,93 @@ + + +
Fetching charity details...
+ + diff --git a/frontend/src/styles/custom-inputs.css b/frontend/src/styles/custom-inputs.css new file mode 100644 index 00000000..52c74a62 --- /dev/null +++ b/frontend/src/styles/custom-inputs.css @@ -0,0 +1,112 @@ +/* Prevent resizing of textarea use normal font*/ +textarea { + resize: none; + font-family: inherit; +} + +/* Remove arrow spinners on number inputs */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +input[type="number"] { + -moz-appearance: textfield; +} + +/* Custom Radio Button Groups */ +.radio-group input[type="radio"] { + /* We don't completely remove this so that we can keep keyboard accessibility */ + opacity: 0; + width: 0; + height: 0; + margin: 0; + padding: 0; + position: absolute; +} +.radio-group input[type="radio"] + label { + flex-grow: 1; + flex-basis: 0; + padding: 0.5rem; + border: 1px solid var(--eaa-blue-600); + text-align: center; +} +.radio-group input:not([disabled])[type="radio"] + label { + cursor: pointer; +} +.radio-group input:not([disabled])[type="radio"]:focus + label { + outline: 2px solid var(--eaa-blue-600); + outline-offset: 1px; +} +.radio-group input[type="radio"]:checked + label { + background-color: var(--eaa-blue-600); + color: white; +} + +/* Custom Select Dropdowns */ +.eaa-select { + /* Safari adds gloss which can only be removed with -webkit-appearance: none; but this removes the arrows. We add the arrows back with an svg background */ + background: url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0Ljk1IDEwIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2ZmZjt9LmNscy0ye2ZpbGw6IzQ0NDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPmFycm93czwvdGl0bGU+PHJlY3QgY2xhc3M9ImNscy0xIiB3aWR0aD0iNC45NSIgaGVpZ2h0PSIxMCIvPjxwb2x5Z29uIGNsYXNzPSJjbHMtMiIgcG9pbnRzPSIxLjQxIDQuNjcgMi40OCAzLjE4IDMuNTQgNC42NyAxLjQxIDQuNjciLz48cG9seWdvbiBjbGFzcz0iY2xzLTIiIHBvaW50cz0iMy41NCA1LjMzIDIuNDggNi44MiAxLjQxIDUuMzMgMy41NCA1LjMzIi8+PC9zdmc+) + no-repeat 99% 50%; + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + width: 100%; + border: 1px solid var(--eaa-blue-600); +} +.eaa-select:hover { + cursor: pointer; +} + +/* Custom Checkboxes */ +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 25px; + width: 25px; + background-color: var(--gray-100); +} +.checkmark:after { + content: ""; + position: absolute; + display: none; +} +.eaa-checkbox { + position: relative; + padding-left: 35px; + user-select: none; + min-height: 25px; +} +.eaa-checkbox input { + position: absolute; + opacity: 0; + height: 0; + width: 0; +} +.eaa-checkbox:hover input ~ .checkmark { + cursor: pointer; +} +.eaa-checkbox:hover input:not(:checked) ~ .checkmark { + background-color: var(--gray-200); +} +.eaa-checkbox input:checked ~ .checkmark { + background-color: var(--eaa-blue-600); +} +.eaa-checkbox input:checked ~ .checkmark:after { + display: block; +} +.eaa-checkbox .checkmark:after { + left: 8px; + top: 2px; + width: 6px; + height: 14px; + border: solid white; + border-width: 0 3px 3px 0; + transform: rotate(45deg); +} +.eaa-checkbox:focus-within .checkmark { + outline: 2px solid var(--eaa-blue-600); + outline-offset: 2px; + border-radius: 1px; +} diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css new file mode 100644 index 00000000..a419358c --- /dev/null +++ b/frontend/src/styles/main.css @@ -0,0 +1,116 @@ +/* Set up Theme colours */ +:host { + --eaa-blue-100: #edf4f6; + --eaa-blue-200: #dcebef; + --eaa-blue-600: #1592a5; + --eaa-blue-700: #187485; + + --gray-100: #e7e7e7; + --gray-200: #d1d1d1; +} + +/* Fix bug where cleantalk honeypot field shows up */ +.apbct_special_field { + display: none; +} + +/* Animations */ +* { + transition-property: color, background-color, border-color, + text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, + backdrop-filter, display; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 250ms; + box-sizing: border-box; + /* This is a new widely available properly that lets you animate `display: none` */ + transition-behavior: allow-discrete; + @starting-style { + opacity: 0; + } +} + +/* Buttons */ +button { + background-color: var(--eaa-blue-600); + font-weight: 700; +} +button:hover { + background-color: var(--eaa-blue-700); + cursor: pointer; +} + +/* Links */ +a { + color: var(--eaa-blue-600); +} + +/* Fieldset - these allow you to group inputs to enable or disable multiple at once */ +fieldset { + padding: 0; + border: none; + margin: 0; +} + +/* Form Validation - shows tick or alert badges based on validity of input */ +.validate input:user-valid, +input:user-valid.validate { + background-image: url("data:image/svg+xml,"); + background-repeat: no-repeat; + background-position: center right 0.5rem; +} +.validate input:user-invalid, +input:user-invalid.validate { + background-image: url("data:image/svg+xml,"); + background-repeat: no-repeat; + background-position: center right 0.5rem; +} + +/* Base popover styling */ +[popover] { + all: unset; /* Reset default browser styles */ + display: block; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + background-color: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(8px); + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + + padding: 32px; + max-width: 480px; + width: 90%; + + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; +} + +/* When popover is open */ +[popover]:popover-open { + opacity: 1; + visibility: visible; +} + +/* Button styling */ +.more-info-button { + background-color: transparent; + border: 1px solid black; + border-radius: 100px; + padding: 0; + cursor: pointer; + transition: background-color 0.2s ease; + width: 18px; + height: 18px; +} + +.more-info-button:hover { + background-color: var(--eaa-blue-200); +} + +/* Optional: Backdrop when popover is open */ +[popover]::backdrop { + background-color: rgba(0, 0, 0, 0.2); +} diff --git a/frontend/src/styles/utilities.css b/frontend/src/styles/utilities.css new file mode 100644 index 00000000..a1ec64cf --- /dev/null +++ b/frontend/src/styles/utilities.css @@ -0,0 +1,126 @@ +/* Width */ +.w-full { + width: 100%; +} + +/* Height */ +.h-12 { + height: 3rem; +} +.h-20 { + height: 5rem; +} + +/* Padding */ +.p-2 { + padding: 0.5rem; +} +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} +.px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; +} + +/* Margin bottom */ +.mb-1 { + margin-bottom: 0.25rem; +} +.mb-2 { + margin-bottom: 0.5rem; +} +.mb-4 { + margin-bottom: 1rem; +} + +/* Margin top */ +.mt-1 { + margin-top: 0.25rem; +} +.mt-4 { + margin-top: 1rem; +} +.mt-8 { + margin-top: 2rem; +} + +/* Borders */ +.border { + border-style: solid; + border-width: 1px; + border-color: var(--eaa-blue-600); +} +.border-none { + border: none; +} + +/* Focus outline */ +.focus-outline:focus, .focus-outline:focus-within { + outline-style: solid; + outline-offset: 1px; + outline-width: 2px; + outline-color: var(--eaa-blue-600); +} + +/* Flex */ +.flex { + display: flex; +} +.flex-col { + flex-direction: column; +} +.flex-grow { + flex-grow: 1; +} +.gap-2 { + gap: 0.5rem; +} +.items-start { + align-items: flex-start; +} +.items-end { + align-items: flex-end; +} +.items-center { + align-items: center; +} +.justify-center { + justify-content: center; +} +.self-stretch { + align-self: stretch; +} + +/* Text sizes */ +.text-sm { + font-size: 0.75rem; +} +.text-md { + font-size: 1rem; +} +.text-lg { + font-size: 1.25rem; +} + +/* Text colors */ +.text-white { + color: white; +} +.text-blue { + color: var(--eaa-blue-700); +} + +/* Border rounding */ +.rounded { + border-radius: 0.125rem; +} + +/* Display */ +.inline-block { + display: inline-block; +} +.block { + display: block; +} \ No newline at end of file diff --git a/frontend/src/thankyou-section.html b/frontend/src/thankyou-section.html new file mode 100644 index 00000000..dc0f620f --- /dev/null +++ b/frontend/src/thankyou-section.html @@ -0,0 +1,28 @@ + + +
+

Thank you!

+

Your receipt will be sent to your email address.

+ +

Any questions?

+ +

+ Please email us at info@eaa.org.au or + call us on +61 492 841 596, if you have any questions. +

+ +

Best wishes and thanks,

+

The team at Effective Altruism Australia

+
+ + \ No newline at end of file diff --git a/frontend/src/total-amount-section.html b/frontend/src/total-amount-section.html new file mode 100644 index 00000000..c1273cab --- /dev/null +++ b/frontend/src/total-amount-section.html @@ -0,0 +1,157 @@ + + + + + diff --git a/frontend/tests/christmas-cards/default-allocation-30-tip.spec.ts b/frontend/tests/christmas-cards/default-allocation-30-tip.spec.ts new file mode 100644 index 00000000..d5154365 --- /dev/null +++ b/frontend/tests/christmas-cards/default-allocation-30-tip.spec.ts @@ -0,0 +1,64 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that a Christmas card is generated with default allocation and 30% tip when the festive gift option is checked. +*/ + +test("Christmas gift: default allocation with 30% tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("1000"); + + await page.getByText("30%", { exact: true }).click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page + .getByLabel("Email", { exact: true }) + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Yes, this is a gift for the festive season" }) + .locator("div") + .click(); + + await page.locator("#festive-gift-section--recipient-name").fill("Test"); + + await page + .locator("#festive-gift-section--recipient-email") + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Personal message (optional,").fill("Test message!"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("eaa-festiveseasoncards.deno.dev")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["paymentReference"]).toMatch( + /^[0-9A-F]{12}$|^cs_test_[A-Za-z0-9]{58}$/ + ); + expect(data["charity"]).toBe("unallocated"); + expect(data["donorName"]).toBe("Nathan"); + expect(data["donorEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["recipientName"]).toBe("Test"); + expect(data["recipientEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["amount"]).toBe("1000"); + expect(data["message"]).toBe("Test message!"); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/christmas-cards/default-allocation-no-tip.spec.ts b/frontend/tests/christmas-cards/default-allocation-no-tip.spec.ts new file mode 100644 index 00000000..a8df4777 --- /dev/null +++ b/frontend/tests/christmas-cards/default-allocation-no-tip.spec.ts @@ -0,0 +1,65 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that a Christmas card is generated with default allocation and no tip +when the festive gift option is checked. +*/ + +test("Christmas gift: default allocation with no tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("1000"); + + await page.getByText("Skip", { exact: true }).click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page + .getByLabel("Email", { exact: true }) + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Yes, this is a gift for the festive season" }) + .locator("div") + .click(); + + await page.locator("#festive-gift-section--recipient-name").fill("Test"); + + await page + .locator("#festive-gift-section--recipient-email") + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Personal message (optional,").fill("Test message!"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("eaa-festiveseasoncards.deno.dev")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["paymentReference"]).toMatch( + /^[0-9A-F]{12}$|^cs_test_[A-Za-z0-9]{58}$/ + ); + expect(data["charity"]).toBe("unallocated"); + expect(data["donorName"]).toBe("Nathan"); + expect(data["donorEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["recipientName"]).toBe("Test"); + expect(data["recipientEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["amount"]).toBe("1000"); + expect(data["message"]).toBe("Test message!"); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/christmas-cards/no-message.spec.ts b/frontend/tests/christmas-cards/no-message.spec.ts new file mode 100644 index 00000000..477a2ef0 --- /dev/null +++ b/frontend/tests/christmas-cards/no-message.spec.ts @@ -0,0 +1,62 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that a Christmas card is generated with default allocation, no tip and no +message is entered. +*/ + +test("Christmas gift: default allocation with no tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("1000"); + + await page.getByText("Skip", { exact: true }).click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page + .getByLabel("Email", { exact: true }) + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Yes, this is a gift for the festive season" }) + .locator("div") + .click(); + + await page.locator("#festive-gift-section--recipient-name").fill("Test"); + + await page + .locator("#festive-gift-section--recipient-email") + .fill("nathan.sherburn@eaa.org.au"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("eaa-festiveseasoncards.deno.dev")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["paymentReference"]).toMatch( + /^[0-9A-F]{12}$|^cs_test_[A-Za-z0-9]{58}$/ + ); + expect(data["charity"]).toBe("unallocated"); + expect(data["donorName"]).toBe("Nathan"); + expect(data["donorEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["recipientName"]).toBe("Test"); + expect(data["recipientEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["amount"]).toBe("1000"); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/christmas-cards/no-recipient-email.spec.ts b/frontend/tests/christmas-cards/no-recipient-email.spec.ts new file mode 100644 index 00000000..d2ecc6ee --- /dev/null +++ b/frontend/tests/christmas-cards/no-recipient-email.spec.ts @@ -0,0 +1,47 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form prompts donors to complete form when their recipient email is empty. +*/ + +test("Christmas gift: submit with empty data", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("1000"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Yes, this is a gift for the festive season" }) + .locator("div") + .click(); + + await page.locator("#festive-gift-section--recipient-name").fill("Test"); + + await page.getByLabel("Personal message (optional,").fill("Test message!"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + // The request should never be sent with invalid data + if (request.url().includes("eaa-festiveseasoncards.deno.dev")) { + expect(true).toBe(false); + } + resolve(); + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/christmas-cards/no-recipient-name.spec.ts b/frontend/tests/christmas-cards/no-recipient-name.spec.ts new file mode 100644 index 00000000..3d62aeff --- /dev/null +++ b/frontend/tests/christmas-cards/no-recipient-name.spec.ts @@ -0,0 +1,49 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form prompts donors to complete form when their recipient name is empty. +*/ + +test("Christmas gift: submit with empty data", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("1000"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Yes, this is a gift for the festive season" }) + .locator("div") + .click(); + + await page + .locator("#festive-gift-section--recipient-email") + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Personal message (optional,").fill("Test message!"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + // The request should never be sent with invalid data + if (request.url().includes("eaa-festiveseasoncards.deno.dev")) { + expect(true).toBe(false); + } + resolve(); + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/christmas-cards/specific-allocation-30-tip.spec.ts b/frontend/tests/christmas-cards/specific-allocation-30-tip.spec.ts new file mode 100644 index 00000000..999cfeeb --- /dev/null +++ b/frontend/tests/christmas-cards/specific-allocation-30-tip.spec.ts @@ -0,0 +1,67 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a Specific allocation is filled +out correctly +*/ + +test("Custom allocation: submit with standard data", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#give-directly-amount").fill("5"); + + await page.getByText("30%", { exact: true }).click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page + .getByLabel("Email", { exact: true }) + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Yes, this is a gift for the festive season" }) + .locator("div") + .click(); + + await page + .locator("#festive-gift-section--recipient-email") + .fill("nathan.sherburn@eaa.org.au"); + + await page.locator("#festive-gift-section--recipient-name").fill("Test"); + + await page.locator("#festive-gift-section--message").fill("Test message!"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("eaa-festiveseasoncards.deno.dev")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["paymentReference"]).toMatch( + /^[0-9A-F]{12}$|^cs_test_[A-Za-z0-9]{58}$/ + ); + expect(data["charity"]).toBe("give-directly"); + expect(data["donorName"]).toBe("Nathan"); + expect(data["donorEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["recipientName"]).toBe("Test"); + expect(data["recipientEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["amount"]).toBe("5"); + expect(data["message"]).toBe("Test message!"); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/christmas-cards/specific-allocation-no-tip.spec.ts b/frontend/tests/christmas-cards/specific-allocation-no-tip.spec.ts new file mode 100644 index 00000000..3ea72020 --- /dev/null +++ b/frontend/tests/christmas-cards/specific-allocation-no-tip.spec.ts @@ -0,0 +1,67 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a Specific allocation is filled +out correctly +*/ + +test("Custom allocation: submit with standard data", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#give-directly-amount").fill("5"); + + await page.getByText("Skip", { exact: true }).click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page + .getByLabel("Email", { exact: true }) + .fill("nathan.sherburn@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Yes, this is a gift for the festive season" }) + .locator("div") + .click(); + + await page + .locator("#festive-gift-section--recipient-email") + .fill("nathan.sherburn@eaa.org.au"); + + await page.locator("#festive-gift-section--recipient-name").fill("Test"); + + await page.locator("#festive-gift-section--message").fill("Test message!"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("eaa-festiveseasoncards.deno.dev")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["paymentReference"]).toMatch( + /^[0-9A-F]{12}$|^cs_test_[A-Za-z0-9]{58}$/ + ); + expect(data["charity"]).toBe("give-directly"); + expect(data["donorName"]).toBe("Nathan"); + expect(data["donorEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["recipientName"]).toBe("Test"); + expect(data["recipientEmail"]).toBe("nathan.sherburn@eaa.org.au"); + expect(data["amount"]).toBe("5"); + expect(data["message"]).toBe("Test message!"); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/communications/all-not-selected.spec.ts b/frontend/tests/communications/all-not-selected.spec.ts new file mode 100644 index 00000000..50beceee --- /dev/null +++ b/frontend/tests/communications/all-not-selected.spec.ts @@ -0,0 +1,68 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that not selecting any of the communications opt-in checkboxes +works as expected. +*/ + +test("Communications: no checkboxes selected", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("5"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Send me news and updates" }) + .locator("div") + .click(); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(false); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBeNull(); + expect(data["form-0-amount"]).toBe("5"); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-1-id"]).toBeNull(); + expect(data["form-1-amount"]).toBe("0.50"); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/communications/all-selected.spec.ts b/frontend/tests/communications/all-selected.spec.ts new file mode 100644 index 00000000..44fa7bd3 --- /dev/null +++ b/frontend/tests/communications/all-selected.spec.ts @@ -0,0 +1,74 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that selecting all of the communications opt-in checkboxes +works as expected. +*/ + +test("Communications: all checkboxes selected", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("5"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Subscribe me to the global Effective" }) + .locator("div") + .click(); + + await page + .locator("label") + .filter({ hasText: "Connect me with my local Effective" }) + .locator("div") + .click(); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(true); + expect(data["connect_to_community"]).toBe(true); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBeNull(); + expect(data["form-0-amount"]).toBe("5"); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-1-id"]).toBeNull(); + expect(data["form-1-amount"]).toBe("0.50"); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/communications/some-selected-some-not.spec.ts b/frontend/tests/communications/some-selected-some-not.spec.ts new file mode 100644 index 00000000..71575707 --- /dev/null +++ b/frontend/tests/communications/some-selected-some-not.spec.ts @@ -0,0 +1,74 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that selecting/deselecting some of the communications opt-in checkboxes +works as expected. +*/ + +test("Communications: checkboxes work", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("5"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + await page + .locator("label") + .filter({ hasText: "Send me news and updates" }) + .locator("div") + .click(); + + await page + .locator("label") + .filter({ hasText: "Connect me with my local" }) + .locator("div") + .click(); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(false); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(true); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBeNull(); + expect(data["form-0-amount"]).toBe("5"); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-1-id"]).toBeNull(); + expect(data["form-1-amount"]).toBe("0.50"); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/default allocation/cancelled-custom-allocation.spec.ts b/frontend/tests/default allocation/cancelled-custom-allocation.spec.ts new file mode 100644 index 00000000..0602d740 --- /dev/null +++ b/frontend/tests/default allocation/cancelled-custom-allocation.spec.ts @@ -0,0 +1,70 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a default allocation is filled +out correctly after filling out the custom allocation section +*/ + +test("Default allocation: cancel custom allocation and submit default allocation with custom amount", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('These specific charities').click(); + + await page.locator('#malaria-consortium-amount').fill('5'); + + await page.locator('#give-directly-amount').fill('5'); + + await page.getByText('The most effective charities^').click(); + + await page.locator('#amount-section--custom-amount-input').fill('1000'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("1000"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("100.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); \ No newline at end of file diff --git a/frontend/tests/default allocation/custom-amount.spec.ts b/frontend/tests/default allocation/custom-amount.spec.ts new file mode 100644 index 00000000..5b8f0d5e --- /dev/null +++ b/frontend/tests/default allocation/custom-amount.spec.ts @@ -0,0 +1,64 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a default allocation with a +custom amount is filled out correctly +*/ + +test("Default allocation: submit a default allocation with custom amount", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('The most effective charities^').click(); + + await page.locator('#amount-section--custom-amount-input').fill('1000'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("1000"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("100.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); \ No newline at end of file diff --git a/frontend/tests/default allocation/custom-then-suggested-then-custom-amount.spec.ts b/frontend/tests/default allocation/custom-then-suggested-then-custom-amount.spec.ts new file mode 100644 index 00000000..84eb4ca0 --- /dev/null +++ b/frontend/tests/default allocation/custom-then-suggested-then-custom-amount.spec.ts @@ -0,0 +1,72 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form sends the right data when you toggle between default +allocation amounts (i.e. suggested amounts and custom amounts) +*/ + +test("Default allocation: submit with custom, suggested, then custom amount", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("The most effective charities^").click(); + + await page.locator('#amount-section--custom-amount-input').fill('1000'); + + await page.getByText('$50').click(); + + await page.getByText('$100').click(); + + await page.locator('#amount-section--custom-amount-input').fill('10'); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("10"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("1.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/default allocation/empty.spec.ts b/frontend/tests/default allocation/empty.spec.ts new file mode 100644 index 00000000..f9f420f4 --- /dev/null +++ b/frontend/tests/default allocation/empty.spec.ts @@ -0,0 +1,39 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form prompts donors to complete form when their default +allocation section is empty. +*/ + +test("Default allocation: submit with empty data", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + page.on('dialog', async dialog => { + expect(dialog.message() === 'Please select an amount of at least $2.'); + await dialog.dismiss(); + }); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + // The request should never be sent with invalid data + if (request.url().includes("pledge_new")) { + expect(true).toBe(false); + } + resolve(); + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); \ No newline at end of file diff --git a/frontend/tests/default allocation/invalid-data.spec.ts b/frontend/tests/default allocation/invalid-data.spec.ts new file mode 100644 index 00000000..d1906ec1 --- /dev/null +++ b/frontend/tests/default allocation/invalid-data.spec.ts @@ -0,0 +1,45 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form doesn't submit with invalid data and prompts the user to +correct the issues. +*/ + +test("Default allocation: submit with invalid data", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("The most effective charities^").click(); + + await page.locator('#amount-section--custom-amount-input').fill('-30'); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + page.on('dialog', async dialog => { + expect(dialog.message() === 'Please select an amount of at least $2.'); + await dialog.dismiss(); + }); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + // The request should never be sent with invalid data + if (request.url().includes("pledge_new")) { + expect(true).toBe(false); + } + resolve(); + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/default allocation/suggested-amount.spec.ts b/frontend/tests/default allocation/suggested-amount.spec.ts new file mode 100644 index 00000000..e82d65fa --- /dev/null +++ b/frontend/tests/default allocation/suggested-amount.spec.ts @@ -0,0 +1,65 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form sends the right data when you choose a suggested amount. +*/ + +test("Default allocation: submit with suggested amount", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("The most effective charities^").click(); + + await page.getByText('$50').click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", async (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("50"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("5.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/default allocation/suggested-then-custom-then-suggested-amount.spec.ts b/frontend/tests/default allocation/suggested-then-custom-then-suggested-amount.spec.ts new file mode 100644 index 00000000..de3fe315 --- /dev/null +++ b/frontend/tests/default allocation/suggested-then-custom-then-suggested-amount.spec.ts @@ -0,0 +1,69 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form sends the right data when you toggle between default +allocation amounts (i.e. suggested amounts and custom amounts) +*/ + +test("Default allocation: submit with suggested, custom, then suggested amount", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("$50").click(); + + await page.locator("#amount-section--custom-amount-input").fill("1"); + + await page.getByText("$100").click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", async (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("100"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("10.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/direct-link allocation/invalid-data.spec.ts b/frontend/tests/direct-link allocation/invalid-data.spec.ts new file mode 100644 index 00000000..6b54c43d --- /dev/null +++ b/frontend/tests/direct-link allocation/invalid-data.spec.ts @@ -0,0 +1,25 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the error page shoes when an invalid direct-linked charity +is chosen through the url params. e.g. /pledge_new/?charity=charity-that-doesnt-exist +*/ + +test("Direct-linked allocation: unknown charity", async ({ page }) => { + let testFinished = new Promise((resolve) => { + page.on("request", async (request) => { + if (request.url().includes("pledge_new")) { + await expect( + page.getByText("Something's gone wrong", { exact: true }) + ).toBeVisible(); + resolve(true); + } + }); + }); + + await page.goto( + "http://localhost:8000/pledge_new/?charity=charity-that-doesnt-exist" + ); + + await testFinished; +}); diff --git a/frontend/tests/direct-link allocation/standard-data.spec.ts b/frontend/tests/direct-link allocation/standard-data.spec.ts new file mode 100644 index 00000000..8d1cc6ef --- /dev/null +++ b/frontend/tests/direct-link allocation/standard-data.spec.ts @@ -0,0 +1,58 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a direct-linked charity is chosen +through the url params. e.g. /pledge_new/?charity=give-directly +*/ + +test("Direct-linked allocation: submit a credit card donation", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/?charity=give-directly"); + + await page.locator("#amount-section--custom-amount-input").fill("28"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("give-directly"); + expect(data["form-0-amount"]).toBe("28"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("2.80"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + } + }); + + await page.getByRole("button", { name: "Donate" }).click(); + +}); diff --git a/frontend/tests/frequency/monthly.spec.ts b/frontend/tests/frequency/monthly.spec.ts new file mode 100644 index 00000000..851f9bb4 --- /dev/null +++ b/frontend/tests/frequency/monthly.spec.ts @@ -0,0 +1,59 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a "monthly" frequency is +selected. +*/ + +test("Frequency: submit a monthly donation with custom amount", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('Monthly').click(); + + await page.locator('#amount-section--custom-amount-input').fill('400'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("monthly"); + expect(data["recurring"]).toBe(true); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("400"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("40.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + } + }); + + await page.getByRole("button", { name: "Donate" }).click(); +}); \ No newline at end of file diff --git a/frontend/tests/frequency/one-time.spec.ts b/frontend/tests/frequency/one-time.spec.ts new file mode 100644 index 00000000..a0fa585a --- /dev/null +++ b/frontend/tests/frequency/one-time.spec.ts @@ -0,0 +1,61 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a "one-time" frequency is +selected (after toggling from "monthly"). +*/ + +test("Frequency: submit a one-time donation with custom amount", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('Monthly').click(); + + await page.getByText('One time').click(); + + await page.locator('#amount-section--custom-amount-input').fill('600'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("600"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("60.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + } + }); + + await page.getByRole("button", { name: "Donate" }).click(); +}); \ No newline at end of file diff --git a/frontend/tests/gift/cancel-a-gift.spec.ts b/frontend/tests/gift/cancel-a-gift.spec.ts new file mode 100644 index 00000000..3df01914 --- /dev/null +++ b/frontend/tests/gift/cancel-a-gift.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that de-selecting "This is a gift" hides the gift recipient fields and +that the form is submitted with the correct data. +*/ + +test("Gifting: cancelling a gift works", async ({ page }) => { + // Placeholder test for when we add gift functionality back + expect(true).toBe(true); +}); diff --git a/frontend/tests/gift/send-a-gift.spec.ts b/frontend/tests/gift/send-a-gift.spec.ts new file mode 100644 index 00000000..d4605c70 --- /dev/null +++ b/frontend/tests/gift/send-a-gift.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that selecting "This is a gift" reveals the gift recipient fields and +that the form is submitted with the correct data. +*/ + +test("Gifting: sending a gift works", async ({ page }) => { + // Placeholder test for when we add gift functionality back + expect(true).toBe(true); +}); diff --git a/frontend/tests/payment-method/bank-transaction-custom-allocation-single-charity.spec.ts b/frontend/tests/payment-method/bank-transaction-custom-allocation-single-charity.spec.ts new file mode 100644 index 00000000..3f5992b2 --- /dev/null +++ b/frontend/tests/payment-method/bank-transaction-custom-allocation-single-charity.spec.ts @@ -0,0 +1,85 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a "bank transactions" is +selected for a specific charity. +*/ + +test("Payment method: submit a bank transaction donation for a specific charity", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#give-directly-amount").fill("66"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.getByText("Bank Transfer", { exact: true }).click(); + + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("bank-transfer"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe(""); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("give-directly"); + expect(data["form-0-amount"]).toBe("66"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("6.60"); + + // Make sure things that shouldn't be sent are not sent + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + } + }); + + let testFinished = new Promise((resolve) => { + page.on("response", async (response) => { + if (response.url().includes("pledge_new")) { + expect(response.status()).toBe(200); + await expect(page.getByText("Thank you, Nathan!")).toBeVisible(); + await expect(page.getByText("$72.60 to:")).toBeVisible({ timeout: 10000 }); + await expect( + page.getByText( + "Your donation will be allocated to your chosen partner charity (or charities)." + ) + ).toBeVisible(); + await page + .locator("#bank-instructions-section--reference") + .textContent() + .then((text) => { + expect(text).toMatch(/^[0-9A-F]{12}$/); + }); + resolve(true); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/payment-method/bank-transaction-custom-allocation.spec.ts b/frontend/tests/payment-method/bank-transaction-custom-allocation.spec.ts new file mode 100644 index 00000000..f836dd22 --- /dev/null +++ b/frontend/tests/payment-method/bank-transaction-custom-allocation.spec.ts @@ -0,0 +1,92 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a "bank transactions" is +selected for a specific charity. +*/ + +test("Payment method: submit a bank transaction donation for a specific selection of charities", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#malaria-consortium-amount").fill("33"); + + await page.locator("#give-directly-amount").fill("66"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.getByText("Bank Transfer", { exact: true }).click(); + + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("bank-transfer"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe(""); + expect(data["form-TOTAL_FORMS"]).toBe(3); + expect(data["form-INITIAL_FORMS"]).toBe(3); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-0-amount"]).toMatch(/^(66|33)$/); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-1-amount"]).toMatch(/^(66|33)$/); + expect(data["form-2-id"]).toBe(null); + expect(data["form-2-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-2-amount"]).toBe("9.90"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-3-id"]).toBe(undefined); + expect(data["form-3-partner_charity"]).toBe(undefined); + expect(data["form-3-amount"]).toBe(undefined); + } + }); + + let testFinished = new Promise((resolve) => { + page.on("response", async (response) => { + if (response.url().includes("pledge_new")) { + expect(response.status()).toBe(200); + await expect(page.getByText("Thank you, Nathan!")).toBeVisible(); + await expect(page.getByText("$108.90 to:")).toBeVisible({ + timeout: 10000, + }); + await expect( + page.getByText( + "Your donation will be allocated to your chosen partner charity (or charities)." + ) + ).toBeVisible(); + await page + .locator("#bank-instructions-section--reference") + .textContent() + .then((text) => { + expect(text).toMatch(/^[0-9A-F]{12}$/); + }); + resolve(true); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/payment-method/bank-transaction-specific-charity.spec.ts b/frontend/tests/payment-method/bank-transaction-specific-charity.spec.ts new file mode 100644 index 00000000..e4334530 --- /dev/null +++ b/frontend/tests/payment-method/bank-transaction-specific-charity.spec.ts @@ -0,0 +1,81 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a "bank transactions" is +selected for a direct-linked charity. +*/ + +test("Payment method: submit a bank transaction donation for a direct-linked charity", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/?charity=give-directly"); + + await page.locator("#amount-section--custom-amount-input").fill("2222"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.getByText("Bank Transfer", { exact: true }).click(); + + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("bank-transfer"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe(""); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("give-directly"); + expect(data["form-0-amount"]).toBe("2222"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("222.20"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + } + }); + + let testFinished = new Promise((resolve) => { + page.on("response", async (response) => { + if (response.url().includes("pledge_new")) { + expect(response.status()).toBe(200); + await expect(page.getByText("Thank you, Nathan!")).toBeVisible(); + await expect(page.getByText("$2444.20 to:")).toBeVisible(); + await expect( + page.getByText("Your donation will be allocated to GiveDirectly.") + ).toBeVisible(); + await page + .locator("#bank-instructions-section--reference") + .textContent() + .then((text) => { + expect(text).toMatch(/^[0-9A-F]{12}$/); + }); + resolve(true); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/payment-method/bank-transaction.spec.ts b/frontend/tests/payment-method/bank-transaction.spec.ts new file mode 100644 index 00000000..2c33da77 --- /dev/null +++ b/frontend/tests/payment-method/bank-transaction.spec.ts @@ -0,0 +1,83 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a "bank transactions" is +selected. +*/ + +test("Payment method: submit a bank transaction donation", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.locator("#amount-section--custom-amount-input").fill("2222"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + await page.getByText("Bank Transfer", { exact: true }).click(); + + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("bank-transfer"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("2222"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("222.20"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + } + }); + + let testFinished = new Promise((resolve) => { + page.on("response", async (response) => { + if (response.url().includes("pledge_new")) { + expect(response.status()).toBe(200); + await expect(page.getByText("Thank you, Nathan!")).toBeVisible(); + await expect(page.getByText("$2444.20 to:")).toBeVisible(); + await expect( + page.getByText( + "Your donation will be allocated to our partner charities." + ) + ).toBeVisible(); + await page + .locator("#bank-instructions-section--reference") + .textContent() + .then((text) => { + expect(text).toMatch(/^[0-9A-F]{12}$/); + }); + resolve(true); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/payment-method/credit-card.spec.ts b/frontend/tests/payment-method/credit-card.spec.ts new file mode 100644 index 00000000..a758100f --- /dev/null +++ b/frontend/tests/payment-method/credit-card.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a "bank transactions" is +selected. +*/ + +test("Payment method: submit a credit card donation", async ({ page }) => { + // Placeholder test for once stripe testing is set up + expect(true).toBe(true); +}); diff --git a/frontend/tests/specific allocation/cancelled-default-allocation-custom-amount.spec.ts b/frontend/tests/specific allocation/cancelled-default-allocation-custom-amount.spec.ts new file mode 100644 index 00000000..c5760c34 --- /dev/null +++ b/frontend/tests/specific allocation/cancelled-default-allocation-custom-amount.spec.ts @@ -0,0 +1,73 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a specific allocation is filled +out correctly after filling out the default allocation section +*/ + +test("Specific allocation: cancel default allocation and submit with standard data", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('The most effective charities^').click(); + + await page.locator('#amount-section--custom-amount-input').fill('1000'); + + await page.getByText('These specific charities').click(); + + await page.locator('#malaria-consortium-amount').fill('5'); + + await page.locator('#give-directly-amount').fill('5'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(3); + expect(data["form-INITIAL_FORMS"]).toBe(3); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-0-amount"]).toBe("5"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-1-amount"]).toBe("5"); + expect(data["form-2-id"]).toBe(null); + expect(data["form-2-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-2-amount"]).toBe("1.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-3-id"]).toBe(undefined); + expect(data["form-3-partner_charity"]).toBe(undefined); + expect(data["form-3-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); \ No newline at end of file diff --git a/frontend/tests/specific allocation/cancelled-default-allocation-suggested-amount.spec.ts b/frontend/tests/specific allocation/cancelled-default-allocation-suggested-amount.spec.ts new file mode 100644 index 00000000..190c3c1d --- /dev/null +++ b/frontend/tests/specific allocation/cancelled-default-allocation-suggested-amount.spec.ts @@ -0,0 +1,73 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a specific allocation is filled +out correctly after filling out the default allocation section (using a suggested amount) +*/ + +test("Specific allocation: cancel default allocation and submit with standard data", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('The most effective charities^').click(); + + await page.getByText('$250').click(); + + await page.getByText('These specific charities').click(); + + await page.locator('#malaria-consortium-amount').fill('5'); + + await page.locator('#give-directly-amount').fill('5'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(3); + expect(data["form-INITIAL_FORMS"]).toBe(3); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-0-amount"]).toBe("5"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-1-amount"]).toBe("5"); + expect(data["form-2-id"]).toBe(null); + expect(data["form-2-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-2-amount"]).toBe("1.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-3-id"]).toBe(undefined); + expect(data["form-3-partner_charity"]).toBe(undefined); + expect(data["form-3-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); \ No newline at end of file diff --git a/frontend/tests/specific allocation/empty.spec.ts b/frontend/tests/specific allocation/empty.spec.ts new file mode 100644 index 00000000..a6a08057 --- /dev/null +++ b/frontend/tests/specific allocation/empty.spec.ts @@ -0,0 +1,29 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form prompts donors to complete form when their Specific +allocation section is less than 2 dollars. +*/ + +test("Specific allocation: submit with empty data", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('These specific charities').click(); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + page.on('dialog', async dialog => { + expect(dialog.message() === 'Please allocate at least $2 across your preferred charities.'); + await dialog.dismiss(); + }); + + await page.getByRole("button", { name: "Donate" }).click(); +}); \ No newline at end of file diff --git a/frontend/tests/specific allocation/invalid-data.spec.ts b/frontend/tests/specific allocation/invalid-data.spec.ts new file mode 100644 index 00000000..70236a56 --- /dev/null +++ b/frontend/tests/specific allocation/invalid-data.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form prompts donors to complete the Specific allocation section +when their data is invalid. +*/ + +test("Custom allocation: submit with invalid data", async ({ page }) => { + + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#malaria-consortium-amount").fill("10"); + + await page.locator("#give-directly-amount").fill("-5"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + // The request should never be sent with invalid data + if (request.url().includes("pledge_new")) { + expect(true).toBe(false); + } + resolve(); + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/specific allocation/standard-data.spec.ts b/frontend/tests/specific allocation/standard-data.spec.ts new file mode 100644 index 00000000..25a5e303 --- /dev/null +++ b/frontend/tests/specific allocation/standard-data.spec.ts @@ -0,0 +1,69 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a Specific allocation is filled +out correctly +*/ + +test("Custom allocation: submit with standard data", async ({ page }) => { + await page.goto('http://localhost:8000/pledge_new/'); + + await page.getByText('These specific charities').click(); + + await page.locator('#malaria-consortium-amount').fill('5'); + + await page.locator('#give-directly-amount').fill('5'); + + await page.getByLabel('First name', {exact:true}).fill('Nathan'); + + await page.getByLabel('Last name').fill('Sherburn'); + + await page.getByLabel('Email', { exact: true }).fill('testing@eaa.org.au'); + + await page.getByLabel('Postcode').fill('3000'); + + await page.locator('#communications-section--referral-sources').selectOption('cant-remember'); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(3); + expect(data["form-INITIAL_FORMS"]).toBe(3); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-0-amount"]).toBe("5"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-1-amount"]).toBe("5"); + expect(data["form-2-id"]).toBe(null); + expect(data["form-2-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-2-amount"]).toBe("1.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-3-id"]).toBe(undefined); + expect(data["form-3-partner_charity"]).toBe(undefined); + expect(data["form-3-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); \ No newline at end of file diff --git a/frontend/tests/thank-you/thank-you.spec.ts b/frontend/tests/thank-you/thank-you.spec.ts new file mode 100644 index 00000000..1fa1c2fe --- /dev/null +++ b/frontend/tests/thank-you/thank-you.spec.ts @@ -0,0 +1,18 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure thankyou page works +*/ + +test("Thank you: ensure thankyou page works", async ({ page }) => { + + await page.goto( + "http://localhost:8000/pledge_new/?thankyou" + ); + + // Check that the "thank you" text is visible on the page + await expect( + page.getByText("Thank you!", { exact: true }) + ).toBeVisible(); + +}); diff --git a/frontend/tests/tipping/basic-donation-30-tip.spec.ts b/frontend/tests/tipping/basic-donation-30-tip.spec.ts new file mode 100644 index 00000000..142c25ee --- /dev/null +++ b/frontend/tests/tipping/basic-donation-30-tip.spec.ts @@ -0,0 +1,64 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form sends the right data when a basic donation with a 30% tip is submitted. +*/ + +test("Tipping: submit with basic donation and 30% tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("$50").click(); + + await page.getByText("30%").click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", async (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("50"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("15.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/tipping/basic-donation-custom-tip.spec.ts b/frontend/tests/tipping/basic-donation-custom-tip.spec.ts new file mode 100644 index 00000000..d1523b46 --- /dev/null +++ b/frontend/tests/tipping/basic-donation-custom-tip.spec.ts @@ -0,0 +1,64 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form sends the right data when a basic donation with a custom tip is submitted. +*/ + +test("Tipping: submit with basic donation and custom tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("$50").click(); + + await page.locator("#amplify-impact-section--custom-amount-input").fill("20"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page.locator("#communications-section--referral-sources").selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", async (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("50"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-1-amount"]).toBe("20.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/tipping/basic-donation-no-tip.spec.ts b/frontend/tests/tipping/basic-donation-no-tip.spec.ts new file mode 100644 index 00000000..b10ead3e --- /dev/null +++ b/frontend/tests/tipping/basic-donation-no-tip.spec.ts @@ -0,0 +1,63 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form sends the right data when a basic donation with no tip is submitted. +*/ + +test("Tipping: submit with basic donation and no tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("$50").click(); + + await page.getByText("Skip", { exact: true }).click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", async (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(1); + expect(data["form-INITIAL_FORMS"]).toBe(1); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toBe("unallocated"); + expect(data["form-0-amount"]).toBe("50"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-1-id"]).toBe(undefined); + expect(data["form-1-partner_charity"]).toBe(undefined); + expect(data["form-1-amount"]).toBe(undefined); + + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/tipping/specific-donation-30-tip.spec.ts b/frontend/tests/tipping/specific-donation-30-tip.spec.ts new file mode 100644 index 00000000..7a1c9346 --- /dev/null +++ b/frontend/tests/tipping/specific-donation-30-tip.spec.ts @@ -0,0 +1,72 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a specific donation is made with a 30% tip +*/ + +test("Tipping: submit with specific donation and 30% tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#malaria-consortium-amount").fill("50"); + + await page.locator("#give-directly-amount").fill("50"); + + await page.getByText("30%").click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(3); + expect(data["form-INITIAL_FORMS"]).toBe(3); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-0-amount"]).toBe("50"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-1-amount"]).toBe("50"); + expect(data["form-2-id"]).toBe(null); + expect(data["form-2-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-2-amount"]).toBe("30.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-3-id"]).toBe(undefined); + expect(data["form-3-partner_charity"]).toBe(undefined); + expect(data["form-3-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/tipping/specific-donation-custom-tip.spec.ts b/frontend/tests/tipping/specific-donation-custom-tip.spec.ts new file mode 100644 index 00000000..06053efa --- /dev/null +++ b/frontend/tests/tipping/specific-donation-custom-tip.spec.ts @@ -0,0 +1,74 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a specific donation is made with a custom tip +*/ + +test("Tipping: submit with specific donation and custom tip", async ({ + page, +}) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#malaria-consortium-amount").fill("50"); + + await page.locator("#give-directly-amount").fill("50"); + + await page.locator("#amplify-impact-section--custom-amount-input").fill("20"); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(3); + expect(data["form-INITIAL_FORMS"]).toBe(3); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-0-amount"]).toBe("50"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-1-amount"]).toBe("50"); + expect(data["form-2-id"]).toBe(null); + expect(data["form-2-partner_charity"]).toBe("eaa-amplify"); + expect(data["form-2-amount"]).toBe("20.00"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-3-id"]).toBe(undefined); + expect(data["form-3-partner_charity"]).toBe(undefined); + expect(data["form-3-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/frontend/tests/tipping/specific-donation-no-tip.spec.ts b/frontend/tests/tipping/specific-donation-no-tip.spec.ts new file mode 100644 index 00000000..9e4ecc98 --- /dev/null +++ b/frontend/tests/tipping/specific-donation-no-tip.spec.ts @@ -0,0 +1,69 @@ +import { expect, test } from "@playwright/test"; + +/* +Ensure that the form submits the correct data when a specific donation is made with no tip +*/ + +test("Tipping: submit with specific donation and no tip", async ({ page }) => { + await page.goto("http://localhost:8000/pledge_new/"); + + await page.getByText("These specific charities").click(); + + await page.locator("#malaria-consortium-amount").fill("50"); + + await page.locator("#give-directly-amount").fill("50"); + + await page.getByText("Skip", { exact: true }).click(); + + await page.getByLabel("First name", { exact: true }).fill("Nathan"); + + await page.getByLabel("Last name").fill("Sherburn"); + + await page.getByLabel("Email", { exact: true }).fill("testing@eaa.org.au"); + + await page.getByLabel("Postcode").fill("3000"); + + await page + .locator("#communications-section--referral-sources") + .selectOption("cant-remember"); + + const testFinished = new Promise((resolve) => { + page.on("request", (request) => { + if (request.url().includes("pledge_new")) { + let data = JSON.parse(request.postData() || "{}"); + expect(data["payment_method"]).toBe("credit-card"); + expect(data["recurring_frequency"]).toBe("one-time"); + expect(data["recurring"]).toBe(false); + expect(data["first_name"]).toBe("Nathan"); + expect(data["last_name"]).toBe("Sherburn"); + expect(data["email"]).toBe("testing@eaa.org.au"); + expect(data["subscribe_to_updates"]).toBe(true); + expect(data["subscribe_to_newsletter"]).toBe(false); + expect(data["connect_to_community"]).toBe(false); + expect(data["how_did_you_hear_about_us_db"]).toBe("cant-remember"); + expect(data["form-TOTAL_FORMS"]).toBe(2); + expect(data["form-INITIAL_FORMS"]).toBe(2); + expect(data["form-0-id"]).toBe(null); + expect(data["form-0-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-0-amount"]).toBe("50"); + expect(data["form-1-id"]).toBe(null); + expect(data["form-1-partner_charity"]).toMatch(/^(malaria-consortium|give-directly)$/); + expect(data["form-1-amount"]).toBe("50"); + + // Make sure things that shouldn't be sent are not sent + expect(data["is_gift"]).toBe(undefined); + expect(data["gift_recipient_name"]).toBe(undefined); + expect(data["gift_recipient_email"]).toBe(undefined); + expect(data["gift_personal_message"]).toBe(undefined); + expect(data["form-2-id"]).toBe(undefined); + expect(data["form-2-partner_charity"]).toBe(undefined); + expect(data["form-2-amount"]).toBe(undefined); + resolve(); + } + }); + }); + + await page.getByRole("button", { name: "Donate" }).click(); + + await testFinished; +}); diff --git a/start_services_dev.sh b/start_services_dev.sh index ca6c7706..d8bf009f 100644 --- a/start_services_dev.sh +++ b/start_services_dev.sh @@ -1,7 +1,13 @@ #!/bin/bash +# Start the virtual environment +source donation_portal_env/bin/activate + # Run stripe webhook listener -echo "Remember to locally run: stripe listen --forward-to localhost:8000/stripe-webhooks/" +echo "**************************************************************" +echo "* If testing Stripe locally, remember run: *" +echo "* stripe listen --forward-to localhost:8000/stripe-webhooks/ *" +echo "**************************************************************" # Start PostgreSQL service sudo service postgresql start @@ -10,7 +16,7 @@ sudo service postgresql start sudo service redis-server start # Start celery -celery -A donation_portal worker -l info & +celery -A donation_portal worker --beat -l INFO & # Apply database migrations python manage.py migrate