fix(cart): say it is a demo where the customer can see it (#195) #200
@@ -5,6 +5,7 @@ import List from 'antd/es/list';
|
||||
import Button from 'antd/es/button';
|
||||
import Empty from 'antd/es/empty';
|
||||
import Card from 'antd/es/card';
|
||||
import Alert from 'antd/es/alert';
|
||||
import Radio from 'antd/es/radio';
|
||||
import Form from 'antd/es/form';
|
||||
import Input from 'antd/es/input';
|
||||
@@ -128,7 +129,10 @@ export default function Cart() {
|
||||
setCheckingOut(true);
|
||||
try {
|
||||
await demoCartPurchase(selectedAddressId);
|
||||
message.success('Order complete!');
|
||||
// The item really is marked sold and everyone who favorited it really is
|
||||
// emailed, so the one moment the customer is told what happened is the one
|
||||
// moment a demo order has to stop looking like a real one. #195.
|
||||
message.success('Demo order complete — nothing was charged and nothing will be shipped.');
|
||||
refreshCartContext();
|
||||
loadAll();
|
||||
} catch (err) {
|
||||
@@ -235,17 +239,38 @@ export default function Cart() {
|
||||
</Card>
|
||||
|
||||
<Card title="Checkout" style={{ marginTop: 24 }}>
|
||||
{/*
|
||||
Shown for the whole of demo mode: before an address is picked,
|
||||
and whether or not PayPal is configured. The word on the button
|
||||
is the smaller half of this — it asks the customer to notice a
|
||||
parenthesis on the control they have already decided to press.
|
||||
*/}
|
||||
{config?.demoMode && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
style={{ marginBottom: 12 }}
|
||||
message="Demonstration only"
|
||||
description="This shop is not taking payments at the moment. Placing an order here costs nothing, and nothing will be shipped."
|
||||
/>
|
||||
)}
|
||||
{!selectedAddressId && <Text type="warning">Select a shipping address to check out.</Text>}
|
||||
{config?.paypalClientId && selectedAddressId && <div id="paypal-cart-buttons" />}
|
||||
{config?.demoMode && selectedAddressId && (
|
||||
<Button
|
||||
block
|
||||
// Secondary when real PayPal buttons sit above it, primary when
|
||||
// it is the only way to check out. The label does not follow
|
||||
// that: a demo order is a demo order either way, and gating the
|
||||
// word on a PayPal client id meant production — which runs demo
|
||||
// mode precisely because it has no PayPal credentials — was the
|
||||
// one place the label never appeared. #195.
|
||||
type={config.paypalClientId ? 'default' : 'primary'}
|
||||
style={{ marginTop: 8 }}
|
||||
loading={checkingOut}
|
||||
onClick={handleDemoCheckout}
|
||||
>
|
||||
{config.paypalClientId ? 'Checkout (Demo)' : 'Checkout'}
|
||||
Checkout (Demo)
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { test, expect, createItem, uniqueSuffix } from './fixtures';
|
||||
|
||||
/**
|
||||
* Demo mode as production actually runs it: `DEMO_MODE=true` with no PayPal
|
||||
* credentials configured, which is the interim #191 put in place and the shape
|
||||
* this suite already runs in.
|
||||
*
|
||||
* That combination used to render a full-width primary button reading plainly
|
||||
* "Checkout". The "(Demo)" suffix was gated on a PayPal client id being present,
|
||||
* so the one configuration that needs the label was the only one that did not
|
||||
* get it — and the button is not decorative, it marks real inventory sold and
|
||||
* emails everyone who favorited it. See #195.
|
||||
*
|
||||
* These assert what a customer can see, because that is the whole defect. The
|
||||
* server was always right about what it was doing.
|
||||
*/
|
||||
|
||||
const ADDRESS = {
|
||||
fullName: 'Dana Holt',
|
||||
addressLine1: '118 Cedar Street',
|
||||
city: 'Asheville',
|
||||
state: 'NC',
|
||||
postalCode: '28801',
|
||||
isDefault: true
|
||||
};
|
||||
|
||||
test.describe('Demo mode says so to the customer', () => {
|
||||
test('labels the checkout button as a demo when no PayPal is configured', async ({
|
||||
page,
|
||||
customer,
|
||||
cart
|
||||
}) => {
|
||||
const name = `Demo c${uniqueSuffix()}`;
|
||||
const item = await createItem(page.request, { name, price: '80' });
|
||||
expect((await page.request.post(`/api/cart/items/${item.id}`)).status()).toBe(201);
|
||||
expect((await page.request.post('/api/customers/me/addresses', { data: ADDRESS })).ok()).toBe(true);
|
||||
|
||||
await cart.goto();
|
||||
|
||||
// The cart selects the default address on load, so the checkout controls
|
||||
// render with nothing clicked.
|
||||
await expect(cart.checkoutButton).toBeVisible({ timeout: 20000 });
|
||||
await expect(cart.checkoutButton).toHaveText('Checkout (Demo)');
|
||||
});
|
||||
|
||||
test('says so before the customer commits, not only on the button', async ({
|
||||
page,
|
||||
customer,
|
||||
cart
|
||||
}) => {
|
||||
const name = `Demo n${uniqueSuffix()}`;
|
||||
const item = await createItem(page.request, { name, price: '80' });
|
||||
expect((await page.request.post(`/api/cart/items/${item.id}`)).status()).toBe(201);
|
||||
expect((await page.request.post('/api/customers/me/addresses', { data: ADDRESS })).ok()).toBe(true);
|
||||
|
||||
await cart.goto();
|
||||
|
||||
// A label on the button alone asks the customer to notice a parenthesis on
|
||||
// the thing they are already clicking. The notice is the part that has to
|
||||
// survive someone not reading carefully.
|
||||
await expect(cart.demoNotice).toBeVisible({ timeout: 20000 });
|
||||
});
|
||||
|
||||
test('the confirmation says nothing was charged', async ({ page, customer, cart }) => {
|
||||
const name = `Demo p${uniqueSuffix()}`;
|
||||
const item = await createItem(page.request, { name, price: '80' });
|
||||
expect((await page.request.post(`/api/cart/items/${item.id}`)).status()).toBe(201);
|
||||
expect((await page.request.post('/api/customers/me/addresses', { data: ADDRESS })).ok()).toBe(true);
|
||||
|
||||
await cart.goto();
|
||||
await expect(cart.checkoutButton).toBeVisible({ timeout: 20000 });
|
||||
|
||||
await cart.checkoutButton.click();
|
||||
|
||||
// "Order complete!" is true and is exactly what a real order would say. The
|
||||
// item really is marked sold and favoriters really are emailed, so the one
|
||||
// moment the customer is told what happened has to distinguish the two.
|
||||
await expect(page.getByText(/nothing was charged/i)).toBeVisible({ timeout: 20000 });
|
||||
});
|
||||
});
|
||||
@@ -10,10 +10,18 @@ import { Locator, Page } from '@playwright/test';
|
||||
export class CartPage {
|
||||
readonly continueShoppingButton: Locator;
|
||||
readonly emptyNotice: Locator;
|
||||
readonly checkoutButton: Locator;
|
||||
readonly demoNotice: Locator;
|
||||
|
||||
constructor(private readonly page: Page) {
|
||||
this.continueShoppingButton = page.getByRole('button', { name: 'Continue Shopping' });
|
||||
this.emptyNotice = page.getByText('Your cart is empty');
|
||||
// Matched on a prefix rather than the whole label, so a test can assert what
|
||||
// the label says instead of having to know it in order to find the button.
|
||||
// That is the point of #195: the label was wrong, and a locator naming it in
|
||||
// full would have gone looking for the right button and found nothing.
|
||||
this.checkoutButton = page.getByRole('button', { name: /^Checkout/ });
|
||||
this.demoNotice = page.getByText('Demonstration only');
|
||||
}
|
||||
|
||||
async goto(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user