Skip to content

Commit dcad45e

Browse files
committed
Add Actions collection helper methods and tests
1 parent 2f10eb4 commit dcad45e

File tree

5 files changed

+349
-14
lines changed

5 files changed

+349
-14
lines changed

src/DataView/Actions.php

+104-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class Actions implements IteratorAggregate {
2121
*
2222
* @var Action[]
2323
*/
24-
private array $actions;
24+
private array $actions = [];
2525

2626
/**
2727
* Creates a collection.
@@ -35,7 +35,32 @@ private function __construct( Action ...$actions ) {
3535
throw new InvalidArgumentException( 'No actions provided.' );
3636
}
3737

38-
$this->actions = $actions;
38+
$this->add_actions( $actions );
39+
}
40+
41+
/**
42+
* Adds provided actions to the collection.
43+
*
44+
* @since $ver$
45+
*
46+
* @param iterable $actions The action.
47+
* @param bool $is_prepended Whether the action should be prepended to the actions list.
48+
*/
49+
private function add_actions( iterable $actions, bool $is_prepended = false ): void {
50+
$list = [];
51+
foreach ( $actions as $action ) {
52+
if ( ! $action instanceof Action ) {
53+
continue;
54+
}
55+
$id = $action->to_array()['id'];
56+
$list[ $id ] = $action;
57+
}
58+
59+
if ( ! $is_prepended ) {
60+
$this->actions = array_merge( $this->actions, $list );
61+
} else {
62+
$this->actions = array_merge( $list, $this->actions );
63+
}
3964
}
4065

4166
/**
@@ -49,6 +74,46 @@ public static function of( Action ...$actions ): self {
4974
return new self( ...$actions );
5075
}
5176

77+
/**
78+
* Returns a collection with extra actions appended.
79+
*
80+
* @since $ver$
81+
*
82+
* @param Action ...$actions The actions.
83+
*
84+
* @return self the collection.
85+
*/
86+
public function append( Action ...$actions ): self {
87+
if ( ! $actions ) {
88+
throw new InvalidArgumentException( 'No actions provided.' );
89+
}
90+
91+
$clone = clone $this;
92+
$clone->add_actions( $actions );
93+
94+
return $clone;
95+
}
96+
97+
/**
98+
* Returns a collection with extra actions prepend.
99+
*
100+
* @since $ver$
101+
*
102+
* @param Action ...$actions The actions.
103+
*
104+
* @return self The collection.
105+
*/
106+
public function prepend( Action ...$actions ): self {
107+
if ( ! $actions ) {
108+
throw new InvalidArgumentException( 'No actions provided.' );
109+
}
110+
111+
$clone = clone $this;
112+
$clone->add_actions( $actions, true );
113+
114+
return $clone;
115+
}
116+
52117
/**
53118
* Returns a serialized set of actions.
54119
*
@@ -68,9 +133,45 @@ public function to_array(): array {
68133
*
69134
* @since $ver$
70135
*
71-
* @return ArrayIterator The iterator.
136+
* @return ArrayIterator<Action> The iterator.
72137
*/
73138
public function getIterator(): ArrayIterator {
74139
return new ArrayIterator( $this->actions );
75140
}
141+
142+
/**
143+
* Merges two actions collections into a new collection.
144+
*
145+
* @since $ver$
146+
*
147+
* @param Actions $other The other actions collection.
148+
*
149+
* @return self The new collection.
150+
*/
151+
public function merge( Actions $other ): Actions {
152+
$clone = clone $this;
153+
$clone->add_actions( $other );
154+
155+
return $clone;
156+
}
157+
158+
/**
159+
* Returns a new collection where the provided action IDs are excluded.
160+
*
161+
* @since $ver$
162+
*
163+
* @param string ...$action_ids The action IDs to exclude.
164+
*
165+
* @return self The new collection.
166+
*/
167+
public function without( string ...$action_ids ): self {
168+
if ( ! $action_ids ) {
169+
throw new InvalidArgumentException( 'No action IDs provided.' );
170+
}
171+
172+
$clone = clone $this;
173+
$clone->actions = array_diff_key( $clone->actions, array_flip( $action_ids ) );
174+
175+
return $clone;
176+
}
76177
}

src/DataView/DataView.php

+42-11
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,9 @@ private function __construct(
158158
$this->id = $id;
159159
$this->views = [ $view ];
160160

161-
$this->filters = $filters;
162-
$this->actions = $actions;
161+
$this->filters( $filters );
162+
$this->actions( $actions );
163+
163164
$this->sort = $sort;
164165
$this->data_source = $data_source;
165166
$this->pagination = Pagination::default();
@@ -581,7 +582,7 @@ public function to_js( bool $is_pretty = false ): string {
581582
/**
582583
* Makes a single result of a DataView visible within a modal.
583584
*
584-
* Note: This method adds a primary action to open a single entry template in a modal.
585+
* Note: This method prepends a primary action to open a single entry template in a modal.
585586
*
586587
* @since $ver$
587588
*
@@ -594,7 +595,6 @@ public function to_js( bool $is_pretty = false ): string {
594595
public function viewable( array $fields, string $label = 'View', ?callable $callback = null ): self {
595596
$this->add_view_fields( ...$fields );
596597

597-
$actions = $this->actions ? iterator_to_array( $this->actions ) : [];
598598
$view_rest_url = sprintf( '{REST_ENDPOINT}/views/%s/data/{id}', $this->id() );
599599

600600
$view_action = Action::modal( 'view', $label, $view_rest_url, true )
@@ -607,9 +607,9 @@ public function viewable( array $fields, string $label = 'View', ?callable $call
607607
}
608608
}
609609

610-
$actions[] = $view_action;
611-
612-
$this->actions = Actions::of( ...$actions );
610+
$this->actions = $this->actions
611+
? $this->actions->prepend( $view_action )
612+
: Actions::of( $view_action );
613613

614614
return $this;
615615
}
@@ -635,7 +635,6 @@ public function deletable( string $label = 'Delete', ?callable $callback = null
635635
return $this;
636636
}
637637

638-
$actions = $this->actions ? iterator_to_array( $this->actions ) : [];
639638
$delete_rest_url = sprintf( '{REST_ENDPOINT}/views/%s/data', $this->id() );
640639

641640
$delete_action = Action::ajax( 'delete', $label, $delete_rest_url, 'DELETE', [ 'id' => '{id}' ], true )
@@ -651,9 +650,9 @@ public function deletable( string $label = 'Delete', ?callable $callback = null
651650
}
652651
}
653652

654-
$actions[] = $delete_action;
655-
656-
$this->actions = Actions::of( ...$actions );
653+
$this->actions = $this->actions
654+
? $this->actions->append( $delete_action )
655+
: Actions::of( $delete_action );
657656

658657
return $this;
659658
}
@@ -761,4 +760,36 @@ private function allowed_fields( array $fields ): array {
761760
)
762761
);
763762
}
763+
764+
/**
765+
* Sets the filters for this view.
766+
*
767+
* @since $ver$
768+
*
769+
* @param Filters|null $filters The filters.
770+
*/
771+
public function filters( ?Filters $filters ): self {
772+
$this->filters = $filters;
773+
774+
return $this;
775+
}
776+
777+
/**
778+
* Sets the actions for this view.
779+
*
780+
* @since $ver$
781+
*
782+
* @param Actions|callable|null $actions The actions.
783+
*/
784+
public function actions( $actions ): self {
785+
if ( is_callable( $actions ) ) {
786+
$actions = $actions( $this->actions ); // Provides the old actions.
787+
}
788+
789+
if ( null === $actions || $actions instanceof Actions ) {
790+
$this->actions = $actions;
791+
}
792+
793+
return $this;
794+
}
764795
}

tests/DataView/ActionsTest.php

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
3+
namespace DataKit\DataViews\Tests\DataView;
4+
5+
use DataKit\DataViews\DataView\Action;
6+
use DataKit\DataViews\DataView\Actions;
7+
use PHPUnit\Framework\TestCase;
8+
9+
/**
10+
* Unit tests for {@see Actions}
11+
*
12+
* @since $ver$
13+
*/
14+
final class ActionsTest extends TestCase {
15+
/**
16+
* Test case for {@see Actions::of()} and {@see Actions::to_array()}.
17+
*
18+
* @since $ver$
19+
*/
20+
public function testOf(): void {
21+
$actions = Actions::of(
22+
$action_1 = Action::url( 'action1', 'Action 1', 'https://some-url.test' )->primary( 'one' ),
23+
$action_2 = Action::url( 'action2', 'Action 2', 'https://some-url.test' ),
24+
);
25+
26+
self::assertCount( 2, $actions );
27+
self::assertSame(
28+
[ 'action1' => $action_1->to_array(), 'action2' => $action_2->to_array() ],
29+
$actions->to_array()
30+
);
31+
32+
$this->expectException( \InvalidArgumentException::class );
33+
Actions::of();
34+
}
35+
36+
/**
37+
* Test case for {@see Actions::getIterator()}.
38+
*
39+
* @since $ver$
40+
*/
41+
public function testGetIterator(): void {
42+
$actions = Actions::of(
43+
$action_1 = Action::url( 'action1', 'Action 1', 'https://some-url.test' )->primary( 'one' ),
44+
$action_2 = Action::url( 'action2', 'Action 2', 'https://some-url.test' ),
45+
);
46+
47+
$result = iterator_to_array( $actions );
48+
49+
self::assertSame( [ 'action1' => $action_1, 'action2' => $action_2 ], $result );
50+
}
51+
52+
/**
53+
* Test case for {@see Actions::append()} and {@see Actions::prepend()}.
54+
*
55+
* @since $ver$
56+
*/
57+
public function testAppendPrepend(): void {
58+
$actions = Actions::of(
59+
Action::url( 'action1', 'Action 1', 'https://some-url.test' )->primary( 'one' ),
60+
Action::url( 'action2', 'Action 2', 'https://some-url.test' ),
61+
);
62+
63+
$append = $actions->append(
64+
Action::url( 'action3', 'Action 3', 'https://some-url.test' ),
65+
Action::url( 'action4', 'Action 4', 'https://some-url.test' ),
66+
);
67+
68+
$prepend = $actions->prepend(
69+
Action::url( 'action3', 'Action 3', 'https://some-url.test' ),
70+
Action::url( 'action4', 'Action 4', 'https://some-url.test' ),
71+
);
72+
73+
self::assertNotSame( $actions, $append );
74+
self::assertNotSame( $actions, $prepend );
75+
self::assertNotSame( $append, $prepend );
76+
77+
self::assertCount( 2, $actions );
78+
self::assertCount( 4, $append );
79+
self::assertCount( 4, $prepend );
80+
81+
self::assertSame( [ 'action3', 'action4', 'action1', 'action2' ], array_keys( $prepend->to_array() ) );
82+
self::assertSame( [ 'action1', 'action2', 'action3', 'action4' ], array_keys( $append->to_array() ) );
83+
}
84+
85+
/**
86+
* Test case for {@see Actions::append()} and {@see Actions::prepend()} without any params.
87+
*
88+
* @since $ver$
89+
*
90+
* @param bool $is_prepend Whether the method to be called is `prepend()`.
91+
*
92+
* @testWith [false, true]
93+
*/
94+
public function testAppendPrependException( bool $is_prepend ): void {
95+
$actions = Actions::of(
96+
Action::url( 'action1', 'Action 1', 'https://some-url.test' ),
97+
);
98+
99+
$this->expectException( \InvalidArgumentException::class );
100+
101+
$is_prepend
102+
? $actions->prepend()
103+
: $actions->append();
104+
}
105+
106+
/**
107+
* Test case for {@see Actions::merge()}.
108+
*
109+
* @since $ver$
110+
*/
111+
public function testMerge(): void {
112+
$actions = Actions::of(
113+
Action::url( 'action1', 'Action 1', 'https://some-url.test' ),
114+
Action::url( 'action2', 'Action 2', 'https://some-url.test' ),
115+
);
116+
117+
$additional = Actions::of(
118+
Action::url( 'action3', 'Action 3', 'https://some-url.test', ),
119+
Action::url( 'action1', 'Overwritten action 1', 'https://overwritten.test' ),
120+
Action::url( 'action4', 'Action 4', 'https://some-url.test' ),
121+
);
122+
123+
$merged = $actions->merge( $additional );
124+
$merged_reversed = $additional->merge( $actions );
125+
126+
self::assertNotSame( $merged, $actions );
127+
self::assertNotSame( $merged, $additional );
128+
self::assertCount( 2, $actions );
129+
self::assertCount( 3, $additional );
130+
self::assertCount( 4, $merged ); // Because action 1 is overwritten.
131+
self::assertCount( 4, $merged_reversed );
132+
133+
self::assertSame( [ 'action1', 'action2', 'action3', 'action4' ], array_keys( $merged->to_array() ) );
134+
self::assertSame( [ 'action3', 'action1', 'action4', 'action2' ], array_keys( $merged_reversed->to_array() ) );
135+
136+
self::assertSame( 'Overwritten action 1', $merged->to_array()['action1']['label'] );
137+
self::assertSame( 'Action 1', $merged_reversed->to_array()['action1']['label'] );
138+
}
139+
140+
/**
141+
* Test case for {@see Actions::without())}.
142+
*
143+
* @since $ver$
144+
*/
145+
public function testWithout(): void {
146+
$actions = Actions::of(
147+
Action::url( 'action1', 'Action 1', 'https://overwritten.test' ),
148+
Action::url( 'action2', 'Action 2', 'https://some-url.test', ),
149+
Action::url( 'action3', 'Action 3', 'https://some-url.test', ),
150+
Action::url( 'action4', 'Action 4', 'https://some-url.test' ),
151+
);
152+
153+
$without = $actions->without( 'action2', 'action3' );
154+
155+
self::assertNotSame( $without, $actions );
156+
self::assertCount( 2, $without );
157+
self::assertSame( [ 'action1', 'action4' ], array_keys( $without->to_array() ) );
158+
159+
$this->expectException( \InvalidArgumentException::class );
160+
$actions->without();
161+
}
162+
}

0 commit comments

Comments
 (0)