Skip to content

Commit

Permalink
feat: support define actions by group (#2) (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
MissLixf authored Dec 4, 2023
1 parent 784a87c commit d132973
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/store/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function Action(
// support string for type
if (typeof action === 'string') {
action = {
type: action as string
type: action
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/store/action/action-definition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ACTION_ID_PREFIX } from '../types';
type ExtractTypeToPayload<T> = T extends [...infer A, never] ? ExtractTypeToPayload<A> : T extends Array<any> ? T : never;
export type ExtractTypeToPayload<T> = T extends [...infer A, never] ? ExtractTypeToPayload<A> : T extends Array<any> ? T : never;

export interface ActionRef<T = any> {
type: string;
Expand Down
30 changes: 30 additions & 0 deletions packages/store/action/action-group-definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ActionCreator, ExtractTypeToPayload, defineAction } from './action-definition';

const payloadSymbol = '__payloadSymbol';

type PayloadRef = { [payloadSymbol]: true };

type ExtractActionCreators<T> = { [key in keyof T]?: ActionCreator<T[key] extends { payload: infer P } ? P : T[key]> };

function isPayloadRef(variable: unknown): variable is PayloadRef {
return typeof variable === 'object' && payloadSymbol in variable && variable[payloadSymbol] === true;
}

export function payload<T1 = never, T2 = never, T3 = never, T4 = never>() {
return {
[payloadSymbol]: true
} as PayloadRef & { payload: ExtractTypeToPayload<[T1, T2, T3, T4]> };
}

export function defineActions<T extends Record<string, PayloadRef>>(groupName: string, actions: T) {
let result: ExtractActionCreators<T> = {};
for (const key in actions) {
if (isPayloadRef(actions[key])) {
const type = `${groupName}_${key}`;
result[key] = defineAction(type) as ActionCreator;
} else {
throw new Error(`${key} is not a PayloadRef, please use payload function define it.`);
}
}
return result;
}
1 change: 1 addition & 0 deletions packages/store/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './references';
export * from './store';
export * from './store-factory';
export * from './action/action-definition';
export { defineActions, payload } from './action/action-group-definition';
export { Dispatcher, dispatch } from './dispatcher';
export { getObjectValue, setObjectValue } from './utils';
export { Id, PaginationInfo, StoreOptions } from './types';
7 changes: 6 additions & 1 deletion packages/store/store/examples/pages/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { defineAction } from '@tethys/store';
import { defineAction, defineActions, payload } from '@tethys/store';

export const updateTitle = defineAction<string, { title: string }>('updateTitle');

export const updateContent = defineAction<string, { content: string }>('updateContent');

export const groupActions = defineActions('page', {
updateTitle: payload<string, { title: string }>(),
updateContent: payload<string, { content: string }>()
});
4 changes: 2 additions & 2 deletions packages/store/store/examples/pages/page-list.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Action, EntityState, EntityStore } from '@tethys/store';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { updateTitle } from './actions';
import { groupActions } from './actions';

export interface Page {
_id: string;
Expand Down Expand Up @@ -34,7 +34,7 @@ export class PagesStore extends EntityStore<PagesState, Page> {
);
}

@Action(updateTitle)
@Action(groupActions.updateTitle)
pureUpdateTitle(_id: string, payload: { title: string }) {
this.update(_id, { title: payload.title });
}
Expand Down
6 changes: 3 additions & 3 deletions packages/store/store/examples/pages/page.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, inject } from '@angular/core';
import { Action, dispatch, Store } from '@tethys/store';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { updateContent, updateTitle } from './actions';
import { groupActions, updateContent, updateTitle } from './actions';

interface PageDetailState {
detail: { _id: string; title: string; content: string };
Expand All @@ -18,7 +18,7 @@ export class PageDetailStore extends Store<PageDetailState> {
super({});
}

@Action(updateTitle)
@Action(groupActions.updateTitle)
pureUpdateTitle(_id: string, payload: { title: string }) {
if (_id === this.snapshot.detail._id) {
this.update({
Expand Down Expand Up @@ -63,7 +63,7 @@ export class PageDetailStore extends Store<PageDetailState> {
updateTitle() {
return of(true).pipe(
tap(() => {
dispatch(updateTitle('1', { title: 'New First Page Title' }));
dispatch(groupActions.updateTitle('1', { title: 'New First Page Title' }));
})
);
}
Expand Down
62 changes: 55 additions & 7 deletions packages/store/test/dispatch-actions.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import { Injectable } from '@angular/core';
import { Action, dispatch, Store, defineAction, EntityState, EntityStore } from '@tethys/store';
import { Action, dispatch, Store, defineAction, EntityState, EntityStore, defineActions, payload } from '@tethys/store';
import { TestBed } from '@angular/core/testing';
import { produce } from '@tethys/cdk';

export const updateTitle = defineAction<string, { title: string }>('updateTitle');
const updateTitle = defineAction<string, { title: string }>('updateTitle');

export const updateContent = defineAction<string, { content: string }>('updateContent');
const updateContent = defineAction<string, { content: string }>('updateContent');

export const upvote = defineAction<string>('like page');
const upvote = defineAction<string>('like page');

export const likePage = defineAction<string>('like page');
const likePage = defineAction<string>('like page');

const commentActions = defineActions('PAGE', {
add: payload<string, { _id: string; content: string }>(),
delete: payload<string, string>()
});

interface Page {
_id: string;
title: string;
voteCount?: number;
commentCount?: number;
}

interface PageDetailState {
detail: { _id: string; title: string; content: string; like?: boolean };
detail: { _id: string; title: string; content: string; like?: boolean; comments?: { _id: string; content: string }[] };
}

const pageDetail = { _id: '1', title: 'First Page Title', content: 'First Page Detail Content' };
const pageDetail = { _id: '1', title: 'First Page Title', content: 'First Page Detail Content', comments: [] };

const pageList = [
{ _id: '1', title: 'First Page Title' },
Expand All @@ -39,6 +46,10 @@ const likePageSpy = jasmine.createSpy('like page');

const upvoteSpy = jasmine.createSpy('upvote');

const detailAddCommentSpy = jasmine.createSpy('detail add comment');

const listAddCommentSpy = jasmine.createSpy('list add comment');

@Injectable({ providedIn: 'root' })
export class PageDetailStore extends Store<PageDetailState> {
static detailSelector(state: PageDetailState) {
Expand Down Expand Up @@ -75,6 +86,17 @@ export class PageDetailStore extends Store<PageDetailState> {
}
}

@Action(commentActions.add)
pureAddComment(_id: string, payload: { _id: string; content: string }) {
detailAddCommentSpy(_id, payload);
this.update({
detail: {
...this.snapshot.detail,
comments: produce(this.snapshot.detail.comments).add(payload)
}
});
}

@Action(likePage)
pureGiveALike(_id: string) {
likePageSpy(_id);
Expand All @@ -87,6 +109,10 @@ export class PageDetailStore extends Store<PageDetailState> {
dispatchUpdateContent(_id: string, payload: { content: string }) {
dispatch(updateContent(_id, payload));
}

dispatchAddComment(_id: string, payload: { _id: string; content: string }) {
dispatch(commentActions.add(_id, payload));
}
}

interface PagesState extends EntityState<Page> {}
Expand All @@ -112,6 +138,15 @@ export class PagesStore extends EntityStore<PagesState, Page> {
}));
}

@Action(commentActions.add)
pureAddComment(_id: string) {
listAddCommentSpy(_id);
this.update(_id, (entity) => ({
...entity,
commentCount: (entity.commentCount || 0) + 1
}));
}

dispatchUpvote(id: string) {
dispatch(upvote(id));
}
Expand Down Expand Up @@ -161,4 +196,17 @@ describe('#dispatchActions', () => {
expect(entity.voteCount).toEqual(1);
});
});

describe('#dispatch group action', () => {
it('should action functions invoke when dispatch', () => {
detailStore = TestBed.inject(PageDetailStore);
listStore = TestBed.inject(PagesStore);
detailStore.dispatchAddComment('1', { _id: 'comment_1', content: 'A New Comment' });
expect(detailAddCommentSpy).toHaveBeenCalledWith('1', { _id: 'comment_1', content: 'A New Comment' });
expect(listAddCommentSpy).toHaveBeenCalledWith('1');
expect(detailStore.snapshot.detail.comments[0]).toEqual({ _id: 'comment_1', content: 'A New Comment' });
const entity = listStore.snapshot.entities.find((item) => item._id === '1');
expect(entity.commentCount).toEqual(1);
});
});
});

0 comments on commit d132973

Please sign in to comment.