diff --git a/src/EventTickets/resources/admin/components/EventTicketsListTable/EventTicketsListTable.module.scss b/src/EventTickets/resources/admin/components/EventTicketsListTable/EventTicketsListTable.module.scss
index ed807500e9..ecb7805295 100644
--- a/src/EventTickets/resources/admin/components/EventTicketsListTable/EventTicketsListTable.module.scss
+++ b/src/EventTickets/resources/admin/components/EventTicketsListTable/EventTicketsListTable.module.scss
@@ -1,3 +1,18 @@
+.listTable {
+ :global {
+ .event-description {
+ p {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+}
+
+
.container {
text-align: center;
color: #424242;
@@ -19,7 +34,7 @@
}
.helpMessage {
- font-size:0.875rem;
+ font-size: 0.875rem;
font-weight: 400;
line-height: 1.57;
}
diff --git a/src/EventTickets/resources/admin/components/EventTicketsListTable/index.tsx b/src/EventTickets/resources/admin/components/EventTicketsListTable/index.tsx
index 84d306dbe0..de252078d1 100644
--- a/src/EventTickets/resources/admin/components/EventTicketsListTable/index.tsx
+++ b/src/EventTickets/resources/admin/components/EventTicketsListTable/index.tsx
@@ -78,9 +78,15 @@ const ListTableBlankSlate = () => {
);
};
+/**
+ * EventTicketsListTable
+ *
+ * @unreleased Add wrapper class for the EventTicketsListTable
+ * @since 3.6.0
+ */
export default function EventTicketsListTable() {
return (
- <>
+
- >
+
);
}
diff --git a/src/EventTickets/resources/admin/components/TicketTypeFormModal/index.tsx b/src/EventTickets/resources/admin/components/TicketTypeFormModal/index.tsx
index 34733d37f1..6cda0e7e08 100644
--- a/src/EventTickets/resources/admin/components/TicketTypeFormModal/index.tsx
+++ b/src/EventTickets/resources/admin/components/TicketTypeFormModal/index.tsx
@@ -1,4 +1,4 @@
-import {SubmitHandler, useForm} from 'react-hook-form';
+import {Controller, SubmitHandler, useForm} from 'react-hook-form';
import {__} from '@wordpress/i18n';
import styles from './TicketTypeFormModal.module.scss';
import FormModal from '../FormModal';
@@ -6,10 +6,13 @@ import {useTicketTypeForm} from './ticketTypeFormContext';
import {Inputs, TicketModalProps} from './types';
import {useEffect} from 'react';
import EventTicketsApi from '../api';
+import CurrencyInput from 'react-currency-input-field';
+import parseValueFromLocale from './parseValueFromLocale';
/**
* Ticket Form Modal component
*
+ * @unreleased Replace number input with CurrencyInput component
* @since 3.6.0
*/
export default function TicketTypeFormModal({isOpen, handleClose, apiSettings, eventId}: TicketModalProps) {
@@ -18,6 +21,7 @@ export default function TicketTypeFormModal({isOpen, handleClose, apiSettings, e
const {
register,
+ control,
handleSubmit,
reset,
formState: {errors, isDirty},
@@ -74,9 +78,29 @@ export default function TicketTypeFormModal({isOpen, handleClose, apiSettings, e
-
+ (
+
+ onChange(parseInt(value) >= 0 ? parseValueFromLocale(value) : '')
+ }
+ />
+ )}
+ />
- {__('Leave empty for', 'give')} {__('free', 'give')}
+ {__('Leave empty for', 'give')}
+ {__('free', 'give')}
diff --git a/src/EventTickets/resources/admin/components/TicketTypeFormModal/parseValueFromLocale.ts b/src/EventTickets/resources/admin/components/TicketTypeFormModal/parseValueFromLocale.ts
new file mode 100644
index 0000000000..76a4462b3b
--- /dev/null
+++ b/src/EventTickets/resources/admin/components/TicketTypeFormModal/parseValueFromLocale.ts
@@ -0,0 +1,24 @@
+/*
+ * @unreleased
+ */
+export default function parseValueFromLocale(amount: string): string {
+ if (!amount) {
+ return amount;
+ }
+
+ const numberFormat = new Intl.NumberFormat(window.navigator.language);
+ const parts = numberFormat.formatToParts(1234.56);
+
+ let groupSeparator: string;
+ let decimalSeparator: string;
+
+ for (const part of parts) {
+ if (part.type === 'group') {
+ groupSeparator = part.value;
+ } else if (part.type === 'decimal') {
+ decimalSeparator = part.value;
+ }
+ }
+
+ return amount.replaceAll(groupSeparator, '').replace(decimalSeparator, '.');
+}
diff --git a/src/EventTickets/resources/admin/components/TicketTypeFormModal/types.ts b/src/EventTickets/resources/admin/components/TicketTypeFormModal/types.ts
index 97e13e0c94..9d48bab8a3 100644
--- a/src/EventTickets/resources/admin/components/TicketTypeFormModal/types.ts
+++ b/src/EventTickets/resources/admin/components/TicketTypeFormModal/types.ts
@@ -21,6 +21,7 @@ export interface TicketModalProps {
apiSettings: {
apiRoot: string;
apiNonce: string;
+ currencyCode: string;
};
eventId: number;
}
diff --git a/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/BlockPlaceholder.tsx b/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/BlockPlaceholder.tsx
index db7f41e146..c2f57bc404 100644
--- a/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/BlockPlaceholder.tsx
+++ b/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/BlockPlaceholder.tsx
@@ -4,6 +4,7 @@ import EventTicketsList from '../../../components/EventTicketsList';
import {getWindowData} from '@givewp/form-builder/common';
/**
+ * @unreleased Hide tickets once the event has ended.
* @since 3.6.0
*/
export default function BlockPlaceholder({attributes}) {
@@ -15,19 +16,25 @@ export default function BlockPlaceholder({attributes}) {
return null;
}
+ const startDateTimeObj = new Date(event.startDateTime);
+ const endDateTimeObj = new Date(event.endDateTime);
+ const hasEnded = endDateTimeObj < new Date();
+
return (
-
+
{event.description && }
-
+ {!hasEnded && (
+
+ )}
);
diff --git a/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/styles.scss b/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/styles.scss
index 72a9c899e1..cce7f1afca 100644
--- a/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/styles.scss
+++ b/src/EventTickets/resources/blocks/EventTicketsBlock/Edit/styles.scss
@@ -65,15 +65,24 @@
padding: var(--givewp-spacing-2);
&__header {
+ background: none;
padding: 0;
- &::before {
- display: none;
- }
-
&__date {
+ background-color: var(--givewp-grey-25);
color: var(--givewp-grey-900);
}
}
+
+ &__tickets {
+ &__ticket {
+ &__quantity {
+ &__sold-out {
+ background-color: var(--givewp-grey-25);
+ color: var(--givewp-grey-900);
+ }
+ }
+ }
+ }
}
}
diff --git a/src/EventTickets/resources/blocks/EventTicketsBlock/types.ts b/src/EventTickets/resources/blocks/EventTicketsBlock/types.ts
index 7ddfcd4752..b91b79eb1d 100644
--- a/src/EventTickets/resources/blocks/EventTicketsBlock/types.ts
+++ b/src/EventTickets/resources/blocks/EventTicketsBlock/types.ts
@@ -3,8 +3,9 @@ import {TicketType} from '../../components/types';
export type EventSettings = {
id: number;
title: string;
- startDateTime: Date;
description: string;
+ startDateTime: Date;
+ endDateTime: Date;
ticketTypes: TicketType[];
};
diff --git a/src/EventTickets/resources/components/EventTicketsHeader.tsx b/src/EventTickets/resources/components/EventTicketsHeader.tsx
index e7a6c7230d..a00ef19ace 100644
--- a/src/EventTickets/resources/components/EventTicketsHeader.tsx
+++ b/src/EventTickets/resources/components/EventTicketsHeader.tsx
@@ -1,9 +1,15 @@
+import {__} from '@wordpress/i18n';
import {format} from 'date-fns';
-export default function EventTicketsHeader({title, startDateTime}) {
+/**
+ * @unreleased Show "ENDED" badge once the event has ended.
+ * @since 3.6.0
+ */
+export default function EventTicketsHeader({title, startDateTime, endDateTime}) {
const fullDate = format(startDateTime, 'EEEE, MMMM do, hh:mmaaa');
const day = format(startDateTime, 'dd');
const month = format(startDateTime, 'MMM');
+ const hasEnded = endDateTime < new Date();
return (
@@ -12,6 +18,12 @@ export default function EventTicketsHeader({title, startDateTime}) {
{title}
{fullDate}
+
+ {hasEnded && (
+
+ {__('Ended', 'give')}
+
+ )}
);
}
diff --git a/src/EventTickets/resources/components/EventTicketsListItem.tsx b/src/EventTickets/resources/components/EventTicketsListItem.tsx
index 20d056ea54..f9ce347fc7 100644
--- a/src/EventTickets/resources/components/EventTicketsListItem.tsx
+++ b/src/EventTickets/resources/components/EventTicketsListItem.tsx
@@ -38,7 +38,9 @@ export default function EventTicketsListItem({ticketType, currency, currencyRate
>
) : (
-
{__('Sold out', 'give')}
+
+ {__('Sold out', 'give')}
+
)}
diff --git a/src/EventTickets/resources/components/types.ts b/src/EventTickets/resources/components/types.ts
index d9c0c91ad3..8d2d2f9388 100644
--- a/src/EventTickets/resources/components/types.ts
+++ b/src/EventTickets/resources/components/types.ts
@@ -4,11 +4,11 @@ export type Event = {
id: number;
name: string;
title: string;
- startDateTime: Date;
description: string;
+ startDateTime: Date;
+ endDateTime: Date;
ticketTypes: TicketType[];
ticketsLabel: string;
- soldOutMessage: string;
};
export type TicketType = {
diff --git a/src/EventTickets/resources/templates/EventTickets/index.tsx b/src/EventTickets/resources/templates/EventTickets/index.tsx
index c68382caa3..d3fbbffb1b 100644
--- a/src/EventTickets/resources/templates/EventTickets/index.tsx
+++ b/src/EventTickets/resources/templates/EventTickets/index.tsx
@@ -5,23 +5,30 @@ import {Event} from '../../components/types';
import './styles.scss';
+/**
+ * @unreleased Hide tickets once the event has ended.
+ * @since 3.6.0
+ */
export default function EventTicketsField({
name,
- id,
title,
- startDateTime,
description,
+ startDateTime,
+ endDateTime,
ticketTypes,
ticketsLabel,
- soldOutMessage,
}: Event) {
+ const startDateTimeObj = new Date(startDateTime);
+ const endDateTimeObj = new Date(endDateTime);
+ const hasEnded = endDateTimeObj < new Date();
+
return (
-
+
{description && }
-
+ {!hasEnded && }
);
}
diff --git a/src/EventTickets/resources/templates/EventTickets/styles.scss b/src/EventTickets/resources/templates/EventTickets/styles.scss
index 2462e9a21f..eba60079a5 100644
--- a/src/EventTickets/resources/templates/EventTickets/styles.scss
+++ b/src/EventTickets/resources/templates/EventTickets/styles.scss
@@ -1,6 +1,7 @@
.givewp-event-tickets {
&__header {
+ background-color: color-mix(in lab, var(--givewp-primary-color) 15%, white);
color: var(--givewp-grey-900);
column-gap: var(--givewp-spacing-2);
display: grid;
@@ -10,21 +11,9 @@
position: relative;
z-index: 1;
- &::before {
- background-color: var(--givewp-primary-color);
- content: "";
- height: 100%;
- left: 0;
- opacity: 0.1;
- position: absolute;
- top: 0;
- width: 100%;
- z-index: -1;
- }
-
&__date {
align-items: center;
- background-color: var(--givewp-grey-25);
+ background-color: var(--givewp-grey-5);
border-radius: var(--givewp-rounded-4);
color: var(--givewp-primary-color);
display: flex;
@@ -62,6 +51,24 @@
line-height: 1.5;
margin: 0;
}
+
+ &__ended {
+ align-items: center;
+ display: flex;
+ grid-column: 3 / 4;
+ grid-row: 1 / 3;
+
+ span {
+ background-color: var(--givewp-primary-color);
+ border-radius: var(--givewp-rounded-4);
+ color: var(--givewp-shades-white);
+ font-size: 0.75rem;
+ font-weight: 600;
+ line-height: 1.5;
+ padding: var(--givewp-spacing-1) var(--givewp-spacing-2);
+ text-transform: uppercase;
+ }
+ }
}
&__description {
@@ -94,11 +101,12 @@
border-radius: var(--givewp-rounded-2);
border: 1px solid var(--givewp-grey-25);
display: flex;
+ gap: var(--givewp-spacing-4);
padding: var(--givewp-spacing-4);
&__description {
display: flex;
- flex: 1 0 auto;
+ flex: 1 0 0;
flex-direction: column;
gap: var(--givewp-spacing-1);
@@ -174,6 +182,16 @@
line-height: 1.5;
margin: 0;
}
+
+ &__sold-out {
+ background-color: color-mix(in lab, var(--givewp-primary-color) 15%, white);
+ border-radius: var(--givewp-rounded-4);
+ color: var(--givewp-primary-color);
+ font-size: 0.75rem;
+ font-weight: 600;
+ line-height: 1.5;
+ padding: var(--givewp-spacing-1) var(--givewp-spacing-2);
+ }
}
}
}
diff --git a/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/DataTransferObjects/StripePaymentIntentData.php b/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/DataTransferObjects/StripePaymentIntentData.php
index 654de1a69f..63243e1377 100644
--- a/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/DataTransferObjects/StripePaymentIntentData.php
+++ b/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/DataTransferObjects/StripePaymentIntentData.php
@@ -33,19 +33,16 @@ class StripePaymentIntentData
* @var string|null
*/
public $applicationFeeAmount;
- /**
- * @var string
- */
- public $statementDescriptor;
/**
* @var string|null
*/
public $receiptEmail;
/**
+ * @unreleased removed statement_descriptor. As of 01/02/2024, Stripe no longer supports the `statement_descriptor` parameter on the PaymentIntent API for PaymentIntents in which one of the supported `payment_method_types` is `card`.
* @since 3.0.0
*
- * @param array{amount: string, currency: string, customer: string, description: string, metadata: array, automatic_payment_methods: array, application_fee_amount: string, statement_descriptor: string, receipt_email: string } $array
+ * @param array{amount: string, currency: string, customer: string, description: string, metadata: array, automatic_payment_methods: array, application_fee_amount: string, receipt_email: string } $array
*/
public static function fromArray(array $array): self
{
@@ -57,7 +54,6 @@ public static function fromArray(array $array): self
$self->metadata = $array['metadata'];
$self->automaticPaymentMethods = !empty($array['automatic_payment_methods']) ? $array['automatic_payment_methods'] : null;
$self->applicationFeeAmount = !empty($array['application_fee_amount']) ? $array['application_fee_amount'] : null;
- $self->statementDescriptor = $array['statement_descriptor'];
$self->receiptEmail = !empty($array['receipt_email']) ? $array['receipt_email'] : null;
return $self;
@@ -73,8 +69,7 @@ public function toParams()
'currency' => $this->currency,
'customer' => $this->customer,
'description' => $this->description,
- 'metadata' => $this->metadata,
- 'statement_descriptor' => $this->statementDescriptor,
+ 'metadata' => $this->metadata
];
if ($this->automaticPaymentMethods){
@@ -102,4 +97,4 @@ public function toOptions(string $stripeConnectAccountId): array
{
return ['stripe_account' => $stripeConnectAccountId];
}
-}
\ No newline at end of file
+}
diff --git a/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/StripePaymentElementRepository.php b/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/StripePaymentElementRepository.php
index 893ce4f9df..107e54a66f 100644
--- a/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/StripePaymentElementRepository.php
+++ b/src/PaymentGateways/Gateways/Stripe/StripePaymentElementGateway/StripePaymentElementRepository.php
@@ -90,6 +90,7 @@ public function getOrCreateStripeCustomerFromDonation(
}
/**
+ * @unreleased removed statement_descriptor. As of 01/02/2024, Stripe no longer supports the `statement_descriptor` parameter on the PaymentIntent API for PaymentIntents in which one of the supported `payment_method_types` is `card`.
* @since 3.0.0
*
* @throws InvalidPropertyName
@@ -115,9 +116,6 @@ protected function getPaymentIntentDataFromDonation(
);
}
- // Add statement descriptor
- $intentArgs['statement_descriptor'] = give_stripe_get_statement_descriptor();
-
// Send Stripe Receipt emails when enabled.
if (give_is_setting_enabled(give_get_option('stripe_receipt_emails'))) {
$intentArgs['receipt_email'] = $donation->email;
diff --git a/tests/Unit/EventTickets/Actions/ConvertEventTicketsBlockToFieldsApiTest.php b/tests/Unit/EventTickets/Actions/ConvertEventTicketsBlockToFieldsApiTest.php
new file mode 100644
index 0000000000..a85550b2ce
--- /dev/null
+++ b/tests/Unit/EventTickets/Actions/ConvertEventTicketsBlockToFieldsApiTest.php
@@ -0,0 +1,64 @@
+create();
+ $event = $ticketType->event;
+
+ $block = BlockModel::make([
+ 'name' => 'givewp/event-tickets',
+ 'attributes' => [
+ 'eventId' => $event->id,
+ ],
+ ]);
+
+ $donationForm = DonationForm::factory()->create();
+
+ $action = give(ConvertEventTicketsBlockToFieldsApi::class);
+ /** @var EventTickets $field */
+ $field = $action($block, $donationForm->id);
+
+ $this->assertEquals('event-tickets', $field->getName());
+ $this->assertEquals('eventTickets', $field->getType());
+ $this->assertEquals('field', $field->getNodeType());
+
+ $expectedAttributes = [
+ 'title' => $event->title,
+ 'startDateTime' => $event->startDateTime->format('Y-m-d H:i:s'),
+ 'endDateTime' => $event->endDateTime->format('Y-m-d H:i:s'),
+ 'description' => $event->description,
+ 'ticketTypes' => [EventTicketTypeData::make($ticketType)->toArray()],
+ ];
+
+ $fieldAttributes = $field->jsonSerialize();
+
+ foreach ($expectedAttributes as $key => $value) {
+ $this->assertEquals($value, $fieldAttributes[$key]);
+ }
+ }
+}
diff --git a/tests/Unit/EventTickets/Routes/CreateEventTest.php b/tests/Unit/EventTickets/Routes/CreateEventTest.php
new file mode 100644
index 0000000000..80118082ab
--- /dev/null
+++ b/tests/Unit/EventTickets/Routes/CreateEventTest.php
@@ -0,0 +1,128 @@
+ 'New Title',
+ 'description' => 'New Description',
+ 'startDateTime' => '2024-01-01 00:00:00',
+ 'endDateTime' => '2024-01-01 23:59:59',
+ ];
+
+ $response = $this->handleRequest($data);
+ $event = Event::find($response->get_data()['id']);
+
+ $this->assertEquals(201, $response->get_status());
+ $this->assertEquals($data['title'], $event->title);
+ $this->assertEquals($data['description'], $event->description);
+ $this->assertEquals(Temporal::toDateTime($data['startDateTime']), $event->startDateTime);
+ $this->assertEquals(Temporal::toDateTime($data['endDateTime']), $event->endDateTime);
+ }
+
+ /**
+ * Test that creating an event with missing required fields returns an error.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventCreationWithMissingRequiredFields()
+ {
+ $response = $this->handleRequest([]);
+
+ $this->assertErrorResponse('rest_missing_callback_param', $response);
+
+ $errorData = $response->as_error()->get_error_data();
+ if (isset($errorData['params'])) {
+ $this->assertContains('title', $errorData['params']);
+ $this->assertContains('startDateTime', $errorData['params']);
+ }
+ }
+
+ /**
+ * Test that creating an event with an invalid date format returns an error.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventCreationWithInvalidDateFormat()
+ {
+ $data = [
+ 'title' => 'New Title',
+ 'startDateTime' => '2024/01/01',
+ ];
+
+ $response = $this->handleRequest($data);
+
+ $this->assertErrorResponse('rest_invalid_param', $response);
+
+ $errorData = $response->as_error()->get_error_data();
+ if (isset($errorData['params'])) {
+ $this->assertArrayHasKey('startDateTime', $errorData['params']);
+ }
+ }
+
+ /**
+ * Test that unauthorized requests are denied.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventCreationRequiresAuthorization()
+ {
+ $data = [
+ 'title' => 'New Title',
+ 'startDateTime' => '2024-01-01 00:00:00',
+ ];
+
+ $response = $this->handleRequest($data, false);
+
+ $this->assertErrorResponse('rest_forbidden', $response, 401);
+ }
+
+ /**
+ * Handle the request common to all tests.
+ *
+ * @unreleased
+ *
+ * @param array $data
+ * @param bool $authenticatedAsAdmin
+ *
+ * @return WP_REST_Response
+ */
+ private function handleRequest(
+ array $data,
+ bool $authenticatedAsAdmin = true
+ ): WP_REST_Response {
+ $request = $this->createRequest(
+ 'POST',
+ "/give-api/v2/events-tickets/events",
+ [],
+ $authenticatedAsAdmin ? 'administrator' : 'anonymous'
+ );
+
+ $request->set_body_params($data);
+
+ return $this->dispatchRequest($request);
+ }
+}
diff --git a/tests/Unit/EventTickets/Routes/CreateEventTicketTypeTest.php b/tests/Unit/EventTickets/Routes/CreateEventTicketTypeTest.php
new file mode 100644
index 0000000000..6b23efc728
--- /dev/null
+++ b/tests/Unit/EventTickets/Routes/CreateEventTicketTypeTest.php
@@ -0,0 +1,135 @@
+create()->id;
+ $data = [
+ 'title' => 'New Title',
+ 'description' => 'New Description',
+ 'price' => 1000,
+ 'capacity' => 100,
+ ];
+
+ $response = $this->handleRequest($eventId, $data);
+ $ticketType = EventTicketType::find($response->get_data()->id);
+
+ $this->assertEquals(201, $response->get_status());
+ $this->assertEquals(1, $ticketType->eventId);
+ $this->assertEquals($eventId, $ticketType->eventId);
+ $this->assertEquals($data['title'], $ticketType->title);
+ $this->assertEquals($data['description'], $ticketType->description);
+ $this->assertEquals(new Money($data['price'], give_get_currency()), $ticketType->price);
+ $this->assertEquals($data['capacity'], $ticketType->capacity);
+ }
+
+ /**
+ * Test that creating an Event Ticket Type giving an invalid event ID returns an error.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventTicketTypeCreationWithInvalidEventId()
+ {
+ $data = [
+ 'title' => 'New Title',
+ 'description' => 'New Description',
+ 'price' => 1000,
+ 'capacity' => 100,
+ ];
+
+ $response = $this->handleRequest(PHP_INT_MAX, $data);
+
+ $this->assertErrorResponse('rest_invalid_param', $response);
+ }
+
+ /**
+ * Test that creating an Event Ticket Type with missing required fields returns an error.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventTicketTypeCreationWithMissingRequiredFields()
+ {
+ $eventId = Event::factory()->create()->id;
+ $response = $this->handleRequest($eventId, []);
+
+ $this->assertErrorResponse('rest_missing_callback_param', $response);
+
+ $errorData = $response->as_error()->get_error_data();
+ if (isset($errorData['params'])) {
+ $this->assertContains('title', $errorData['params']);
+ $this->assertContains('price', $errorData['params']);
+ $this->assertContains('capacity', $errorData['params']);
+ }
+ }
+
+ /**
+ * Test that unauthorized requests are denied.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventTicketTypeCreationRequiresAuthorization()
+ {
+ $eventId = Event::factory()->create()->id;
+ $data = [
+ 'title' => 'New Title',
+ 'price' => '1000',
+ 'capacity' => 100,
+ ];
+
+ $response = $this->handleRequest($eventId, $data, false);
+
+ $this->assertErrorResponse('rest_forbidden', $response, 401);
+ }
+
+ /**
+ * Handle the request common to all tests.
+ *
+ * @unreleased
+ *
+ * @param int $eventId
+ * @param array $data
+ * @param bool $authenticatedAsAdmin
+ *
+ * @return WP_REST_Response
+ */
+ private function handleRequest(
+ int $eventId,
+ array $data,
+ bool $authenticatedAsAdmin = true
+ ): WP_REST_Response {
+ $request = $this->createRequest(
+ 'POST',
+ "/give-api/v2/events-tickets/event/{$eventId}/ticket-types",
+ [],
+ $authenticatedAsAdmin ? 'administrator' : 'anonymous'
+ );
+
+ $request->set_body_params($data);
+
+ return $this->dispatchRequest($request);
+ }
+}
diff --git a/tests/Unit/EventTickets/Routes/DeleteEventTicketTypeTest.php b/tests/Unit/EventTickets/Routes/DeleteEventTicketTypeTest.php
new file mode 100644
index 0000000000..d5af5a9a43
--- /dev/null
+++ b/tests/Unit/EventTickets/Routes/DeleteEventTicketTypeTest.php
@@ -0,0 +1,117 @@
+create()->id;
+
+ $response = $this->handleRequest($ticketTypeId);
+
+ $this->assertEquals(200, $response->get_status());
+ $this->assertNull(EventTicketType::find($ticketTypeId));
+ }
+
+ /**
+ * Test that an invalid ticket type ID returns an error.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventTicketTypeDeletionWithInvalidId()
+ {
+ $response = $this->handleRequest(PHP_INT_MAX);
+
+ $this->assertErrorResponse('rest_invalid_param', $response);
+ }
+
+ /**
+ * Test that a ticket type with associated tickets cannot be deleted.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventTicketTypeDeletionPreventedByTickets()
+ {
+ $eventTicket = EventTicket::factory()->create();
+ $ticketTypeId = $eventTicket->ticketTypeId;
+
+ $response = $this->handleRequest($ticketTypeId);
+
+ $this->assertErrorResponse('rest_invalid_param', $response);
+
+ $errorData = $response->as_error()->get_error_data();
+ if (isset($errorData['params'])) {
+ $this->assertContains(
+ 'ticket_type_id',
+ array_keys($errorData['params'])
+ );
+ $this->assertEquals(
+ 'event_ticket_type_sold_delete_failed',
+ $errorData['details']['ticket_type_id']['code']
+ );
+ $this->assertEquals(
+ 403,
+ $errorData['details']['ticket_type_id']['data']['status']
+ );
+ }
+ }
+
+ /**
+ * Test that unauthorized requests are denied.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventTicketTypeDeletionRequiresAuthorization()
+ {
+ $ticketTypeId = EventTicketType::factory()->create()->id;
+
+ $response = $this->handleRequest($ticketTypeId, false);
+
+ $this->assertErrorResponse('rest_forbidden', $response, 401);
+ }
+
+ /**
+ * Handle the request common to all tests.
+ *
+ * @unreleased
+ *
+ * @param int $ticketTypeId
+ * @param bool $authenticatedAsAdmin
+ *
+ * @return WP_REST_Response
+ */
+ private function handleRequest(
+ int $ticketTypeId,
+ bool $authenticatedAsAdmin = true
+ ): WP_REST_Response {
+ $request = $this->createRequest(
+ 'DELETE',
+ "/give-api/v2/events-tickets/ticket-type/{$ticketTypeId}",
+ [],
+ $authenticatedAsAdmin ? 'administrator' : 'anonymous'
+ );
+
+ return $this->dispatchRequest($request);
+ }
+}
diff --git a/tests/Unit/EventTickets/Routes/DeleteEventsListTableTest.php b/tests/Unit/EventTickets/Routes/DeleteEventsListTableTest.php
new file mode 100644
index 0000000000..7b6fb4d6a7
--- /dev/null
+++ b/tests/Unit/EventTickets/Routes/DeleteEventsListTableTest.php
@@ -0,0 +1,107 @@
+count(3)->create();
+ $eventIds = array_map(function ($event) {
+ return $event->id;
+ }, $events);
+
+ $response = $this->handleRequest($eventIds);
+
+ $this->assertEquals(200, $response->get_status());
+ $this->assertEquals(0, give(EventRepository::class)->prepareQuery()->whereIn('id', $eventIds)->count());
+ }
+
+ /**
+ * Test that an invalid Event ID returns it in the errors array.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventDeletionWithInvalidId()
+ {
+ $response = $this->handleRequest([PHP_INT_MAX]);
+
+ $this->assertCount(1, $response->get_data()['errors']);
+ }
+
+ /**
+ * Test that an event with associated tickets cannot be deleted.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventDeletionPreventedByTickets()
+ {
+ $eventTicket = EventTicket::factory()->create();
+ $eventId = $eventTicket->eventId;
+
+ $response = $this->handleRequest([$eventId]);
+
+ $this->assertCount(1, $response->get_data()['errors']);
+ }
+
+ /**
+ * Test that unauthorized requests are denied.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventDeletionRequiresAuthorization()
+ {
+ $eventId = Event::factory()->create()->id;
+
+ $response = $this->handleRequest([$eventId], false);
+
+ $this->assertErrorResponse('rest_forbidden', $response, 401);
+ }
+
+ /**
+ * Handle the request common to all tests.
+ *
+ * @unreleased
+ *
+ * @param int[] $eventIds
+ * @param bool $authenticatedAsAdmin
+ *
+ * @return WP_REST_Response
+ */
+ private function handleRequest(
+ array $eventIds,
+ bool $authenticatedAsAdmin = true
+ ): WP_REST_Response {
+ $request = $this->createRequest(
+ 'DELETE',
+ "/give-api/v2/events-tickets/events/list-table",
+ [],
+ $authenticatedAsAdmin ? 'administrator' : 'anonymous'
+ );
+
+ $request->set_param('ids', implode(',', $eventIds));
+
+ return $this->dispatchRequest($request);
+ }
+}
diff --git a/tests/Unit/EventTickets/Routes/UpdateEventTest.php b/tests/Unit/EventTickets/Routes/UpdateEventTest.php
index 5251d035b3..9b8229c0ea 100644
--- a/tests/Unit/EventTickets/Routes/UpdateEventTest.php
+++ b/tests/Unit/EventTickets/Routes/UpdateEventTest.php
@@ -1,54 +1,90 @@
create([
+ 'title' => 'Old Title',
+ ]);
- $request->set_param('event_id', $eventId);
+ $response = $this->handleRequest($event->id, ['title' => 'New Title']);
- return $request;
+ $this->assertEquals(200, $response->get_status());
+ $this->assertEquals('New Title', Event::find($event->id)->title);
}
/**
- * @since 3.6.0
+ * Test that an invalid Event ID returns an error.
*
- * @return void
+ * @unreleased
* @throws Exception
*/
- public function testShouldUpdateEventTitle()
+ public function testEventUpdateWithInvalidId()
{
- $event = Event::factory()->create([
- 'title' => 'Old Title',
- ]);
+ $response = $this->handleRequest(PHP_INT_MAX, ['title' => 'New Title']);
- $mockRequest = $this->getMockRequest($event->id);
- $mockRequest->set_param('title', 'New Title');
- $response = (new UpdateEvent())->handleRequest($mockRequest);
+ $this->assertErrorResponse('rest_invalid_param', $response);
+ }
- $this->assertEquals('New Title', Event::find($event->id)->title);
+ /**
+ * Test that unauthorized requests are denied.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventUpdateRequiresAuthorization()
+ {
+ $eventId = Event::factory()->create()->id;
+
+ $response = $this->handleRequest($eventId, ['title' => 'New Title'], false);
+
+ $this->assertErrorResponse('rest_forbidden', $response, 401);
+ }
+
+ /**
+ * Handle the request common to all tests.
+ *
+ * @unreleased
+ *
+ * @param int $eventId
+ * @param array $data
+ * @param bool $authenticatedAsAdmin
+ *
+ * @return WP_REST_Response
+ */
+ private function handleRequest(
+ int $eventId,
+ array $data,
+ bool $authenticatedAsAdmin = true
+ ): WP_REST_Response {
+ $request = $this->createRequest(
+ 'POST',
+ "/give-api/v2/events-tickets/event/{$eventId}",
+ [],
+ $authenticatedAsAdmin ? 'administrator' : 'anonymous'
+ );
+
+ $request->set_body_params($data);
+
+ return $this->dispatchRequest($request);
}
}
diff --git a/tests/Unit/EventTickets/Routes/UpdateEventTicketTypeTest.php b/tests/Unit/EventTickets/Routes/UpdateEventTicketTypeTest.php
index 656e60c4e7..5ab4df7f76 100644
--- a/tests/Unit/EventTickets/Routes/UpdateEventTicketTypeTest.php
+++ b/tests/Unit/EventTickets/Routes/UpdateEventTicketTypeTest.php
@@ -1,76 +1,109 @@
create([
+ 'title' => 'Old Title',
+ ]);
- $request->set_param('ticket_type_id', $id);
+ $response = $this->handleRequest($ticketType->id, ['title' => 'New Title']);
- return $request;
+ $this->assertEquals(200, $response->get_status());
+ $this->assertEquals('New Title', EventTicketType::find($ticketType->id)->title);
}
/**
- * @since 3.6.0
+ * Test that a valid request successfully updates an Event Ticket Type's price.
*
- * @return void
+ * @unreleased
* @throws Exception
*/
- public function testShouldUpdateTicketTypeTitle()
+ public function testEventTicketTypePriceUpdateSuccess()
{
$ticketType = EventTicketType::factory()->create([
- 'title' => 'Old Title',
+ 'price' => new Money(1000, 'USD'),
]);
- $mockRequest = $this->getMockRequest($ticketType->id);
- $mockRequest->set_param('title', 'New Title');
- $response = (new UpdateEventTicketType())->handleRequest($mockRequest);
+ $response = $this->handleRequest($ticketType->id, ['price' => 2000]);
- $this->assertEquals('New Title', EventTicketType::find($ticketType->id)->title);
+ $this->assertEquals(200, $response->get_status());
+ $this->assertEquals(2000, EventTicketType::find($ticketType->id)->price->formatToMinorAmount());
}
/**
- * @since 3.6.0
+ * Test that an invalid Event Ticket Type ID returns an error.
*
- * @return void
+ * @unreleased
* @throws Exception
*/
- public function testShouldUpdateTicketTypePrice()
+ public function testEventTicketTypeUpdateWithInvalidId()
{
- $ticketType = EventTicketType::factory()->create([
- 'price' => new Money(1000, 'USD'),
- ]);
+ $response = $this->handleRequest(PHP_INT_MAX, ['title' => 'New Title']);
- $mockRequest = $this->getMockRequest($ticketType->id);
- $mockRequest->set_param('price', 2000);
- $response = (new UpdateEventTicketType())->handleRequest($mockRequest);
+ $this->assertErrorResponse('rest_invalid_param', $response);
+ }
- $this->assertEquals(2000, EventTicketType::find($ticketType->id)->price->formatToMinorAmount());
+ /**
+ * Test that unauthorized requests are denied.
+ *
+ * @unreleased
+ * @throws Exception
+ */
+ public function testEventUpdateRequiresAuthorization()
+ {
+ $ticketTypeId = EventTicketType::factory()->create()->id;
+
+ $response = $this->handleRequest($ticketTypeId, ['title' => 'New Title'], false);
+
+ $this->assertErrorResponse('rest_forbidden', $response, 401);
+ }
+
+ /**
+ * Handle the request common to all tests.
+ *
+ * @unreleased
+ *
+ * @param int $ticketTypeId
+ * @param array $data
+ * @param bool $authenticatedAsAdmin
+ *
+ * @return WP_REST_Response
+ */
+ private function handleRequest(
+ int $ticketTypeId,
+ array $data,
+ bool $authenticatedAsAdmin = true
+ ): WP_REST_Response {
+ $request = $this->createRequest(
+ 'POST',
+ "/give-api/v2/events-tickets/ticket-type/{$ticketTypeId}",
+ [],
+ $authenticatedAsAdmin ? 'administrator' : 'anonymous'
+ );
+
+ $request->set_body_params($data);
+
+ return $this->dispatchRequest($request);
}
}