diff --git a/assets/css/src/listing.styl b/assets/css/src/listing.styl index 75374fa702..4c9299e65d 100644 --- a/assets/css/src/listing.styl +++ b/assets/css/src/listing.styl @@ -1,24 +1,9 @@ .mailpoet_listing_loading tbody tr, .mailpoet_form_loading tbody tr - opacity: 0.2; - -.widefat tfoot td.mailpoet_check_column, -.widefat thead td.mailpoet_check_column - padding-top: 10px; - -.widefat tbody th.mailpoet_check_column, -.widefat tfoot td.mailpoet_check_column, -.widefat thead td.mailpoet_check_column - padding: 11px 0 0 3px; - -.widefat .mailpoet_check_column - padding: 6px 0 25px; - vertical-align: top; - width: 2.2em; + opacity: 0.2 .mailpoet_select_all background-color: #f1f1f1 .mailpoet_select_all td - text-align: center - + text-align: center \ No newline at end of file diff --git a/assets/js/src/form/form.jsx b/assets/js/src/form/form.jsx index 5808517a42..e6d25d42b7 100644 --- a/assets/js/src/form/form.jsx +++ b/assets/js/src/form/form.jsx @@ -34,7 +34,7 @@ define( name={ this.props.field.name } id={ 'field_'+this.props.field.name } value={ this.props.item[this.props.field.name] } - onChange={ this.props.onValueChange } > + onChange={ this.props.onValueChange }> {options} ); @@ -228,6 +228,7 @@ define( loading: false, item: {} }); + this.refs.form.getDOMNode().reset(); } else { this.loadItem(props.params.id); } @@ -315,6 +316,7 @@ define( return (
diff --git a/assets/js/src/listing/header.jsx b/assets/js/src/listing/header.jsx index 317f8d4e09..2892c45aba 100644 --- a/assets/js/src/listing/header.jsx +++ b/assets/js/src/listing/header.jsx @@ -22,7 +22,8 @@ define(['react', 'classnames'], function(React, classNames) { if(this.props.is_selectable === true) { checkbox = ( - + diff --git a/assets/js/src/listing/listing.jsx b/assets/js/src/listing/listing.jsx index d66e739c3e..22e0c0e658 100644 --- a/assets/js/src/listing/listing.jsx +++ b/assets/js/src/listing/listing.jsx @@ -3,6 +3,7 @@ define( 'mailpoet', 'jquery', 'react', + 'react-router', 'classnames', 'listing/bulk_actions.jsx', 'listing/header.jsx', @@ -15,6 +16,7 @@ define( MailPoet, jQuery, React, + Router, classNames, ListingBulkActions, ListingHeader, @@ -23,7 +25,14 @@ define( ListingGroups, ListingFilters ) { - var ListingItem = React.createClass({ + var Link = Router.Link; + + var ListingItem = React.createClass({ + getInitialState: function() { + return { + toggled: true + }; + }, handleSelectItem: function(e) { var is_checked = jQuery(e.target).is(':checked'); @@ -34,13 +43,18 @@ define( return !e.target.checked; }, + handleDeleteItem: function(id) { + this.props.onDeleteItem(id); + }, + handleToggleItem: function(id) { + this.setState({ toggled: !this.state.toggled }); + }, render: function() { - var checkbox = false; if(this.props.is_selectable === true) { checkbox = ( - + +
+ + Edit + +  |  + + + Trash + + +
+ + + ); + + var row_classes = classNames({ 'is-expanded': !this.state.toggled }) + return ( - + { checkbox } - { this.props.onRenderItem(this.props.item) } + { this.props.onRenderItem(this.props.item, item_actions) } ); } @@ -124,6 +163,7 @@ define( columns={ this.props.columns } onSelectItem={ this.props.onSelectItem } onRenderItem={ this.props.onRenderItem } + onDeleteItem={ this.props.onDeleteItem } selection={ this.props.selection } is_selectable={ this.props.is_selectable } key={ 'item-' + item.id } @@ -170,19 +210,29 @@ define( search: this.state.search, sort_by: this.state.sort_by, sort_order: this.state.sort_order - }, - onSuccess: function(response) { - if(this.isMounted()) { - this.setState({ - items: response.items || [], - filters: response.filters || [], - groups: response.groups || [], - count: response.count || 0, - loading: false - }); - } - }.bind(this) - }); + } + }).done(function(response) { + if(this.isMounted()) { + this.setState({ + items: response.items || [], + filters: response.filters || [], + groups: response.groups || [], + count: response.count || 0, + loading: false + }); + } + }.bind(this)); + }, + handleDeleteItem: function(id) { + this.setState({ loading: true }); + + MailPoet.Ajax.post({ + endpoint: this.props.endpoint, + action: 'delete', + data: id + }).done(function() { + this.getItems(); + }.bind(this)); }, handleSearch: function(search) { this.setState({ @@ -269,8 +319,8 @@ define( this.getItems(); }.bind(this)); }, - handleRenderItem: function(item) { - return this.props.onRenderItem(item); + handleRenderItem: function(item, actions) { + return this.props.onRenderItem(item, actions); }, render: function() { var items = this.state.items, @@ -326,6 +376,7 @@ define( 0 } onSelectItem={ this.handleSelectItem } diff --git a/assets/js/src/newsletters/form.jsx b/assets/js/src/newsletters/form.jsx index 5573e5b4f2..2ba24f52de 100644 --- a/assets/js/src/newsletters/form.jsx +++ b/assets/js/src/newsletters/form.jsx @@ -34,7 +34,6 @@ define( var NewsletterForm = React.createClass({ render: function() { - return ( { newsletter.subject } - -
- - Edit - -
+ { actions } { newsletter.created_at } diff --git a/assets/js/src/segments/list.jsx b/assets/js/src/segments/list.jsx index 2b119a8dde..d38c7a8944 100644 --- a/assets/js/src/segments/list.jsx +++ b/assets/js/src/segments/list.jsx @@ -1,18 +1,14 @@ define( [ 'react', - 'react-router', 'listing/listing.jsx', - 'classnames', + 'classnames' ], function( React, - Router, Listing, classNames ) { - var Link = Router.Link; - var columns = [ { name: 'name', @@ -32,7 +28,7 @@ define( ]; var SegmentList = React.createClass({ - renderItem: function(segment) { + renderItem: function(segment, actions) { var rowClasses = classNames( 'manage-column', 'column-primary', @@ -45,12 +41,7 @@ define( { segment.name } - -
- - Edit - -
+ { actions } { segment.created_at } diff --git a/assets/js/src/subscribers/form.jsx b/assets/js/src/subscribers/form.jsx index dc751d4b7b..63fb31ac33 100644 --- a/assets/js/src/subscribers/form.jsx +++ b/assets/js/src/subscribers/form.jsx @@ -31,8 +31,8 @@ define( label: 'Status', type: 'select', values: { - 'subscribed': 'Subscribed', 'unconfirmed': 'Unconfirmed', + 'subscribed': 'Subscribed', 'unsubscribed': 'Unsubscribed' } } diff --git a/assets/js/src/subscribers/list.jsx b/assets/js/src/subscribers/list.jsx index bead11cec3..abf7a77931 100644 --- a/assets/js/src/subscribers/list.jsx +++ b/assets/js/src/subscribers/list.jsx @@ -1,18 +1,14 @@ define( [ 'react', - 'react-router', 'listing/listing.jsx', - 'classnames', + 'classnames' ], function( React, - Router, Listing, classNames ) { - var Link = Router.Link; - var columns = [ { name: 'email', @@ -62,8 +58,8 @@ define( ]; var List = React.createClass({ - renderItem: function(subscriber) { - var rowClasses = classNames( + renderItem: function(subscriber, actions) { + var row_classes = classNames( 'manage-column', 'column-primary', 'has-row-actions' @@ -87,20 +83,11 @@ define( return (
- + { subscriber.email } - -
- - Edit - -
- - + { actions } { subscriber.first_name } @@ -125,7 +112,6 @@ define( ); diff --git a/lib/Models/Newsletter.php b/lib/Models/Newsletter.php index 9ff33ef241..2f2d915fca 100644 --- a/lib/Models/Newsletter.php +++ b/lib/Models/Newsletter.php @@ -1,7 +1,7 @@ addValidations('subject', array( - 'required' => 'subject_is_blank', - 'isString' => 'subject_is_not_string' - )); - $this->addValidations('body', array( - 'required' => 'body_is_blank', - 'isString' => 'body_is_not_string' + 'required' => __('You need to specify a subject.') )); } @@ -36,7 +31,7 @@ class Newsletter extends Model { static function group($orm, $group = null) { } - public static function createOrUpdate($data = array()) { + static function createOrUpdate($data = array()) { $newsletter = false; if(isset($data['id']) && (int)$data['id'] > 0) { diff --git a/lib/Models/Segment.php b/lib/Models/Segment.php index a78bcd994c..d03242e8fc 100644 --- a/lib/Models/Segment.php +++ b/lib/Models/Segment.php @@ -4,18 +4,17 @@ namespace MailPoet\Models; if(!defined('ABSPATH')) exit; class Segment extends Model { - public static $_table = MP_SEGMENTS_TABLE; + static $_table = MP_SEGMENTS_TABLE; function __construct() { parent::__construct(); $this->addValidations('name', array( - 'required' => 'name_is_blank', - 'isString' => 'name_is_not_string' + 'required' => __('You need to specify a name.') )); } - public function subscribers() { + function subscribers() { return $this->has_many_through( __NAMESPACE__.'\Subscriber', __NAMESPACE__.'\SubscriberSegment', @@ -41,7 +40,7 @@ class Segment extends Model { static function group($orm, $group = null) { } - public static function createOrUpdate($data = array()) { + static function createOrUpdate($data = array()) { $segment = false; if(isset($data['id']) && (int)$data['id'] > 0) { diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index 51b0ad8c22..a3e0e399ff 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -62,7 +62,7 @@ class Subscriber extends Model { return $orm->where('status', $group); } - public function segments() { + function segments() { return $this->has_many_through( __NAMESPACE__.'\Segment', __NAMESPACE__.'\SubscriberSegment', @@ -71,7 +71,7 @@ class Subscriber extends Model { ); } - public static function createOrUpdate($data = array()) { + static function createOrUpdate($data = array()) { $subscriber = false; if(isset($data['id']) && (int)$data['id'] > 0) { diff --git a/lib/Router/Newsletters.php b/lib/Router/Newsletters.php index 41efce2963..dcca8b14dd 100644 --- a/lib/Router/Newsletters.php +++ b/lib/Router/Newsletters.php @@ -45,12 +45,15 @@ class Newsletters { } } - function update($args) { - - } - function delete($id) { + $newsletter = Newsletter::findOne($id); + if($newsletter !== false) { + $result = $newsletter->delete(); + } else { + $result = false; + } + wp_send_json($result); } function send($id) { diff --git a/lib/Router/Segments.php b/lib/Router/Segments.php index 99c7fdbf38..8b5113b79f 100644 --- a/lib/Router/Segments.php +++ b/lib/Router/Segments.php @@ -43,11 +43,14 @@ class Segments { } } - function update($args) { - - } - function delete($id) { + $segment = Segment::findOne($id); + if($segment !== false) { + $result = $segment->delete(); + } else { + $result = false; + } + wp_send_json($result); } } diff --git a/lib/Router/Subscribers.php b/lib/Router/Subscribers.php index efd41d7c9c..af48cc18fe 100644 --- a/lib/Router/Subscribers.php +++ b/lib/Router/Subscribers.php @@ -39,11 +39,13 @@ class Subscribers { wp_send_json($result); } - function update($data) { - - } - function delete($id) { - + $subscriber = Subscriber::findOne($id); + if($subscriber !== false) { + $result = $subscriber->delete(); + } else { + $result = false; + } + wp_send_json($result); } } diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php index a2d58b3b80..91d11c9018 100644 --- a/tests/_bootstrap.php +++ b/tests/_bootstrap.php @@ -15,6 +15,6 @@ $models = array( ); $destroy = function ($model) { Model::factory('\MailPoet\Models\\' . $model) - ->delete_many(); + ->deleteMany(); }; array_map($destroy, $models); diff --git a/tests/acceptance/NewslettersPageCest.php b/tests/acceptance/NewslettersPageCest.php index 102423a4cb..132a23ce4c 100644 --- a/tests/acceptance/NewslettersPageCest.php +++ b/tests/acceptance/NewslettersPageCest.php @@ -3,63 +3,64 @@ use Helper\Acceptance; class NewslettersPageCest { - + function _before(AcceptanceTester $I) { $I->login(); $I->resizeWindow(1024, 768); - $this->firstElementInList = '//*[@id="newsletters"]/div/div/table/tbody/tr[1]'; - $this->waitTime = 2; + $this->first_row = 'id("newsletters")//table/tbody/tr[2]'; + $this->timeout = 3; } - + function iCanSeeTheTitle(AcceptanceTester $I) { $I->amOnPage('/wp-admin/admin.php?page=mailpoet-newsletters'); $I->see('Newsletters'); } - - function iCanAddNewsletterFromListingPage(AcceptanceTester $I) { - $I->waitForElement('.no-items', $this->waitTime); + + function iCanAddANewsletter(AcceptanceTester $I) { + $I->amOnPage('/wp-admin/admin.php?page=mailpoet-newsletters'); + $I->see('No newsletters found'); $I->click('New', '#newsletters'); - $I->fillField('Subject', 'first newsletter'); + $I->fillField('subject', 'first newsletter'); $I->fillField('Body', 'some body'); $I->click('Save'); - $I->waitForText('1 item', $this->waitTime); + $I->waitForText('1 item', $this->timeout); } - - function iCanAddNewsletterFromNewNewsletterPage(AcceptanceTester $I) { - $I->amOnPage('/wp-admin/admin.php?page=mailpoet-newsletters#/form'); - $I->fillField('Subject', 'second newsletter'); + + function iCanAddAnotherNewsletter(AcceptanceTester $I) { + $I->amOnPage('/wp-admin/admin.php?page=mailpoet-newsletters#/new'); + $I->fillField('subject', 'second newsletter'); $I->fillField('Body', 'some body'); $I->click('Save'); - $I->waitForText('2 item', $this->waitTime); + $I->waitForText('2 item', $this->timeout); } - + function iCanSortNewsletterBySubject(AcceptanceTester $I) { $I->click('Subject'); - $I->waitForText('first', $this->waitTime, $this->firstElementInList); + $I->waitForText('first', $this->timeout, $this->first_row); $I->click('Subject'); - $I->waitForText('second', $this->waitTime, $this->firstElementInList); + $I->waitForText('second', $this->timeout, $this->first_row); } - + function iCanSortNewsletterByCreatedDate(AcceptanceTester $I) { $I->click('Created on'); - $I->waitForText('first', $this->waitTime, $this->firstElementInList); + $I->waitForText('first', $this->timeout, $this->first_row); $I->click('Created on'); - $I->waitForText('second', $this->waitTime, $this->firstElementInList); + $I->waitForText('second', $this->timeout, $this->first_row); } - + function iCanSearchNewsletters(AcceptanceTester $I) { - $searchTerm = 'second'; - $I->fillField('Search', $searchTerm); + $search_term = 'second'; + $I->fillField('Search', $search_term); $I->click('Search'); - $I->waitForText($searchTerm, $this->waitTime, $this->firstElementInList); + $I->waitForText($search_term, $this->timeout, $this->first_row); } function iCanSeeMobileView(AcceptanceTester $I) { - $listingHeadings = '//*[@id="newsletters"]/div/div/table/thead'; + $listing_header = 'id("newsletters")//table/thead'; $I->resizeWindow(640, 480); - $I->dontSee('Created on', $listingHeadings); - $I->dontSee('Last modified', $listingHeadings); - $I->see('Subject', $listingHeadings); + $I->dontSee('Created on', $listing_header); + $I->dontSee('Last modified', $listing_header); + $I->see('Subject', $listing_header); } function _after(AcceptanceTester $I) { diff --git a/tests/unit/Models/NewsletterCest.php b/tests/unit/Models/NewsletterCest.php index 66dd75c3e1..651734f9c7 100644 --- a/tests/unit/Models/NewsletterCest.php +++ b/tests/unit/Models/NewsletterCest.php @@ -4,28 +4,33 @@ use MailPoet\Models\Newsletter; class NewsletterCest { function _before() { - $this->before_time = time(); - $this->data = array( - 'subject' => 'My First Newsletter', - 'body' => 'a verrryyyyy long body :)' - ); - - $newsletter = Newsletter::create(); - $newsletter->hydrate($this->data); - $this->saved = $newsletter->save(); } - function itCanBeCreated() { - expect($this->saved)->equals(true); + function itCanCreateOrUpdate() { + $is_created = Newsletter::createOrUpdate(array( + 'subject' => 'new newsletter' + )); + expect($is_created)->equals(true); + + $newsletter = Newsletter::where('subject', 'new newsletter')->findOne(); + expect($newsletter->subject)->equals('new newsletter'); + + $is_updated = Newsletter::createOrUpdate(array( + 'id' => $newsletter->id, + 'subject' => 'updated newsletter' + )); + $newsletter = Newsletter::where('subject', 'updated newsletter')->findOne(); + expect($newsletter->subject)->equals('updated newsletter'); } function itHasASearchFilter() { - $newsletter = Newsletter::filter('search', 'first')->findOne(); - expect($newsletter->subject)->equals($this->data['subject']); + Newsletter::createOrUpdate(array('subject' => 'search for "pineapple"')); + $newsletter = Newsletter::filter('search', 'pineapple')->findOne(); + expect($newsletter->subject)->contains('pineapple'); } function _after() { ORM::for_table(Newsletter::$_table) - ->delete_many(); + ->deleteMany(); } } diff --git a/tests/unit/Models/SegmentCest.php b/tests/unit/Models/SegmentCest.php index 3828e650ab..ce653df518 100644 --- a/tests/unit/Models/SegmentCest.php +++ b/tests/unit/Models/SegmentCest.php @@ -25,7 +25,7 @@ class SegmentCest { $empty_model = Segment::create(); expect($empty_model->save())->notEquals(true); $validations = $empty_model->getValidationErrors(); - expect(count($validations))->equals(2); + expect(count($validations))->equals(1); } function itHasACreatedAtOnCreation() { @@ -62,20 +62,20 @@ class SegmentCest { } function itCanCreateOrUpdate() { - $data = array( - 'name' => 'some other new name' - ); - $createNewRecord = Segment::createOrUpdate($data); + $is_created = Segment::createOrUpdate(array( + 'name' => 'new list' + )); + expect($is_created)->equals(true); - $data = array( - 'name' => $this->data['name'], - 'name_updated' => 'updated name', - ); - $updateExistingRecord = Segment::createOrUpdate($data); + $segment = Segment::where('name', 'new list')->findOne(); + expect($segment->name)->equals('new list'); - $allRecords = Segment::find_array(); - expect(count($allRecords))->equals(2); - expect($allRecords[0]['name'])->equals($data['name_updated']); + $is_updated = Segment::createOrUpdate(array( + 'id' => $segment->id, + 'name' => 'updated list' + )); + $segment = Segment::where('name', 'updated list')->findOne(); + expect($segment->name)->equals('updated list'); } function itCanHaveMultipleSubscribers() { @@ -108,12 +108,12 @@ class SegmentCest { } function _after() { - ORM::for_table(Segment::$_table) - ->delete_many(); - ORM::for_table(Subscriber::$_table) - ->delete_many(); - ORM::for_table(SubscriberSegment::$_table) - ->delete_many(); + ORM::forTable(Segment::$_table) + ->deleteMany(); + ORM::forTable(Subscriber::$_table) + ->deleteMany(); + ORM::forTable(SubscriberSegment::$_table) + ->deleteMany(); } diff --git a/tests/unit/Models/SettingCest.php b/tests/unit/Models/SettingCest.php index 55f82a22bc..dafa8117c7 100644 --- a/tests/unit/Models/SettingCest.php +++ b/tests/unit/Models/SettingCest.php @@ -80,7 +80,7 @@ class SettingCest { } function _after() { - ORM::for_table(Setting::$_table) - ->delete_many(); + ORM::forTable(Setting::$_table) + ->deleteMany(); } } diff --git a/tests/unit/Models/SubscriberCest.php b/tests/unit/Models/SubscriberCest.php index 6edf90bcda..3db206081f 100644 --- a/tests/unit/Models/SubscriberCest.php +++ b/tests/unit/Models/SubscriberCest.php @@ -6,7 +6,6 @@ use MailPoet\Models\SubscriberSegment; class SubscriberCest { function _before() { - $this->before_time = time(); $this->data = array( 'first_name' => 'John', 'last_name' => 'Mailer', @@ -143,11 +142,11 @@ class SubscriberCest { } function _after() { - ORM::for_table(Subscriber::$_table) - ->delete_many(); - ORM::for_table(Segment::$_table) - ->delete_many(); - ORM::for_table(SubscriberSegment::$_table) - ->delete_many(); + ORM::forTable(Subscriber::$_table) + ->deleteMany(); + ORM::forTable(Segment::$_table) + ->deleteMany(); + ORM::forTable(SubscriberSegment::$_table) + ->deleteMany(); } }