Skip to content

Commit

Permalink
feat(generators): add blueprint for CRUD module (#467)
Browse files Browse the repository at this point in the history
closes #442
  • Loading branch information
devCrossNet committed Oct 1, 2019
1 parent 04fbabb commit 05ee8d9
Show file tree
Hide file tree
Showing 24 changed files with 602 additions and 223 deletions.
17 changes: 8 additions & 9 deletions .vuesion/generators/connected/connected.spec.ts.hbs
@@ -1,30 +1,29 @@
import { createLocalVue, mount } from '@vue/test-utils';{{#if wantVuex}} import { createLocalVue, mount } from '@vue/test-utils';
import Vuex, { Store } from 'vuex'; import Vuex, { Store } from 'vuex';
import { i18n } from '@/app/shared/plugins/i18n/i18n'; import { i18n } from '@/app/shared/plugins/i18n/i18n';
import {{ properCase componentName }} from './{{ properCase componentName }}.vue'; import {{ properCase componentName }} from './{{ properCase componentName }}.vue';
import { I{{ properCase componentName }}State } from '../state'; import { I{{ properCase singularName }}State } from '../state';
import { {{ properCase componentName }}Module } from '../module';{{/if}} import { {{ properCase singularName }}Module } from '../module';


const localVue = createLocalVue();{{#if wantVuex}} const localVue = createLocalVue();{{#if wantVuex}}


localVue.use(Vuex);{{/if}} localVue.use(Vuex);{{/if}}


describe('{{ properCase componentName }}.vue', () => { describe('{{ properCase componentName }}.vue', () => {
{{#if wantVuex}} let store: Store<I{{ properCase singularName }}State>;
let store: Store<I{{ properCase componentName }}State>;


beforeEach(() => { beforeEach(() => {
store = new Vuex.Store({ store = new Vuex.Store({
modules: { modules: {
{{ camelCase moduleName }}: {{ properCase componentName }}Module, {{ camelCase singularName }}: {{ properCase singularName }}Module,
}, },
} as any); } as any);
}); });


{{/if}} test('renders component', () => { test('renders component', () => {
const wrapper = mount<any>({{ properCase componentName }}, { const wrapper = mount<any>({{ properCase componentName }}, {
{{#if wantVuex}} store, store,
{{/if}} localVue, localVue,
i18n, i18n,
stubs: ['router-link'], stubs: ['router-link'],
}); });
Expand Down
33 changes: 22 additions & 11 deletions .vuesion/generators/connected/connected.vue.hbs
@@ -1,6 +1,8 @@
<template> <template>
<div :class="$style.{{ camelCase componentName }}"> <div :class="$style.{{ camelCase componentName }}">
<vue-grid> <vue-grid>
<vue-breadcrumb :items="breadCrumbItems"></vue-breadcrumb>

<vue-grid-row> <vue-grid-row>
<vue-grid-item fill> <vue-grid-item fill>
<vue-headline level="1">{{ properCase componentName }}</vue-headline> <vue-headline level="1">{{ properCase componentName }}</vue-headline>
Expand All @@ -10,35 +12,45 @@
</div> </div>
</template> </template>


<script lang="ts">{{#if wantVuex}} <script lang="ts">
import { registerModule } from '@/app/store'; import { registerModule } from '@/app/store';
import { {{ properCase moduleName }}Module } from '../module'; import { IPreLoad } from '@/server/isomorphic';
import { IPreLoad } from '@/server/isomorphic';{{/if}}
import VueGrid from '@/app/shared/components/VueGrid/VueGrid.vue'; import VueGrid from '@/app/shared/components/VueGrid/VueGrid.vue';
import VueBreadcrumb from '@components/VueBreadcrumb/VueBreadcrumb.vue';
import VueGridRow from '@/app/shared/components/VueGridRow/VueGridRow.vue';
import VueGridItem from '@/app/shared/components/VueGridItem/VueGridItem.vue'; import VueGridItem from '@/app/shared/components/VueGridItem/VueGridItem.vue';
import VueButton from '@/app/shared/components/VueButton/VueButton.vue'; import VueButton from '@/app/shared/components/VueButton/VueButton.vue';
import VueGridRow from '@/app/shared/components/VueGridRow/VueGridRow.vue';
import VueHeadline from '@/app/shared/components/VueHeadline/VueHeadline.vue'; import VueHeadline from '@/app/shared/components/VueHeadline/VueHeadline.vue';
import { {{ properCase singularName }}Module } from '../module';
export default { export default {
metaInfo: { metaInfo: {
title: '{{ properCase componentName }}', title: '{{ properCase componentName }}',
}, },
components: { components: {
VueGrid, VueGrid,
VueBreadcrumb,
VueGridRow,
VueGridItem, VueGridItem,
VueButton, VueButton,
VueGridRow,
VueHeadline, VueHeadline,
}, },
data: (): any => ({}), data: (): any => ({}),
methods: {}, methods: {},
computed: {},{{#if wantVuex}} computed: {
breadCrumbItems() {
return [
{ label: this.$t('common.home' /* Home */), href: '/' },
{ label: this.$t('common.{{ properCase singularName }}' /* {{ properCase singularName }} */), href: '/{{ dashCase singularName }}' },
];
},
},
beforeCreate() { beforeCreate() {
registerModule('{{ camelCase moduleName }}', {{ properCase moduleName }}Module); registerModule('{{ camelCase singularName }}', {{ properCase singularName }}Module);
}, },
prefetch: (options: IPreLoad) => { prefetch: (options: IPreLoad) => {
registerModule('{{ camelCase moduleName }}', {{ properCase moduleName }}Module); registerModule('{{ camelCase singularName }}', {{ properCase singularName }}Module);
/** /**
* This is the function where you can load all the data that is needed * This is the function where you can load all the data that is needed
Expand All @@ -47,13 +59,13 @@ export default {
* This function always returns a promise that means, if you want to * This function always returns a promise that means, if you want to
* call a vuex action you have to return it, here is an example * call a vuex action you have to return it, here is an example
* *
* return options.store.dispatch('myAction'); * return options.store.dispatch('fetch{{ properCase singularName }}', '1');
* *
* If you need to fetch data from multiple source your can also return * If you need to fetch data from multiple source your can also return
* a Promise chain or a Promise.all() * a Promise chain or a Promise.all()
*/ */
return Promise.resolve(); return Promise.resolve();
},{{/if}} },
}; };
</script> </script>


Expand All @@ -62,7 +74,6 @@ export default {
.{{ camelCase componentName }} { .{{ camelCase componentName }} {
margin-top: $nav-bar-height; margin-top: $nav-bar-height;
padding: $space-32 0;
min-height: 500px; min-height: 500px;
} }
</style> </style>
146 changes: 146 additions & 0 deletions .vuesion/generators/crud-module/actions.spec.ts.hbs
@@ -0,0 +1,146 @@
import { ActionContext, Commit, Dispatch } from 'vuex';
import MockAdapter from 'axios-mock-adapter';
import { IState } from '@/app/state';
import { HttpService } from '@/app/shared/services/HttpService/HttpService';
import { {{ properCase singularName }}Actions } from './actions';
import { {{ properCase singularName }}DefaultState, I{{ properCase singularName }}State } from './state';

describe('{{ properCase singularName }}Actions', () => {
let testContext: ActionContext<I{{ properCase singularName }}State, IState>;
let mockAxios: MockAdapter;

beforeEach(() => {
testContext = {
dispatch: jest.fn() as Dispatch,
commit: jest.fn() as Commit,
state: {{ properCase singularName }}DefaultState(),
} as ActionContext<I{{ properCase singularName }}State, IState>;

mockAxios = new MockAdapter(HttpService);
});

describe('fetch{{ properCase pluralName }}', () => {
test('it should call SET_{{ constantCase pluralName }} on success', async () => {
const commitMock: jest.Mock = testContext.commit as jest.Mock;
const expected = {};

mockAxios.onGet('/{{ singularName }}').reply(200, expected);

await {{ properCase singularName }}Actions.fetch{{ properCase pluralName }}(testContext);

const actual = commitMock.mock.calls[0];

expect(actual).toEqual(['SET_{{ constantCase pluralName }}', expected]);
});

test('it should throw an error on failure', async () => {
mockAxios.onGet('/{{ singularName }}').reply(500);

try {
await {{ properCase singularName }}Actions.fetch{{ properCase pluralName }}(testContext);
} catch (e) {
expect(e.message).toEqual('Request failed with status code 500');
}
});
});

describe('fetch{{ properCase singularName }}', () => {
test('it should call SET_CURRENT_{{ constantCase singularName }} on success', async () => {
const commitMock: jest.Mock = testContext.commit as jest.Mock;
const expected = {};

mockAxios.onGet('/{{ singularName }}/1').reply(200, expected);

await {{ properCase singularName }}Actions.fetch{{ properCase singularName }}(testContext, '1');

const actual = commitMock.mock.calls[0];

expect(actual).toEqual(['SET_CURRENT_{{ constantCase singularName }}', expected]);
});

test('it should throw an error on failure', async () => {
mockAxios.onGet('/{{ singularName }}/1').reply(500);

try {
await {{ properCase singularName }}Actions.fetch{{ properCase singularName }}(testContext, '1');
} catch (e) {
expect(e.message).toEqual('Request failed with status code 500');
}
});
});

describe('add{{ properCase singularName }}', () => {
test('it should call ADD_{{ constantCase singularName }} on success', async () => {
const commitMock: jest.Mock = testContext.commit as jest.Mock;
const expected = {};

mockAxios.onPost('/{{ singularName }}').reply(200, expected);

await {{ properCase singularName }}Actions.add{{ properCase singularName }}(testContext, expected);

const actual = commitMock.mock.calls[0];

expect(actual).toEqual(['ADD_{{ constantCase singularName }}', expected]);
});

test('it should throw an error on failure', async () => {
mockAxios.onPost('/{{ singularName }}').reply(500);

try {
await {{ properCase singularName }}Actions.add{{ properCase singularName }}(testContext, {});
} catch (e) {
expect(e.message).toEqual('Request failed with status code 500');
}
});
});

describe('update{{ properCase singularName }}', () => {
test('it should call UPDATE_{{ constantCase singularName }} on success', async () => {
const commitMock: jest.Mock = testContext.commit as jest.Mock;
const expected = { id: '1' };

mockAxios.onPut('/{{ singularName }}/1').reply(200, expected);

await {{ properCase singularName }}Actions.update{{ properCase singularName }}(testContext, expected);

const actual = commitMock.mock.calls[0];

expect(actual).toEqual(['UPDATE_{{ constantCase singularName }}', expected]);
});

test('it should throw an error on failure', async () => {
mockAxios.onPut('/{{ singularName }}/1').reply(500);

try {
await {{ properCase singularName }}Actions.update{{ properCase singularName }}(testContext, { id: '1' });
} catch (e) {
expect(e.message).toEqual('Request failed with status code 500');
}
});
});

describe('delete{{ properCase singularName }}', () => {
test('it should call DELETE_{{ constantCase singularName }} on success', async () => {
const commitMock: jest.Mock = testContext.commit as jest.Mock;
const expected = { id: '1' };

mockAxios.onDelete('/{{ singularName }}/1').reply(200, expected);

await {{ properCase singularName }}Actions.delete{{ properCase singularName }}(testContext, expected);

const actual = commitMock.mock.calls[0];

expect(actual).toEqual(['DELETE_{{ constantCase singularName }}', expected]);
});

test('it should throw an error on failure', async () => {
mockAxios.onDelete('/{{ singularName }}/1').reply(500);

try {
await {{ properCase singularName }}Actions.delete{{ properCase singularName }}(testContext, { id: '1' });
} catch (e) {
expect(e.message).toEqual('Request failed with status code 500');
}
});
});
});
56 changes: 56 additions & 0 deletions .vuesion/generators/crud-module/actions.ts.hbs
@@ -0,0 +1,56 @@
import { ActionContext } from 'vuex';
import { IState } from '@/app/state';
import { HttpService } from '@/app/shared/services/HttpService/HttpService';
import { I{{ properCase singularName }}State } from './state';
import { I{{ properCase singularName }} } from './I{{ properCase singularName }}';

export interface I{{ properCase singularName }}Actions {
fetch{{ properCase pluralName }}(context: ActionContext<I{{ properCase singularName }}State, IState>): Promise<any>;
fetch{{ properCase singularName }}(context: ActionContext<I{{ properCase singularName }}State, IState>, id: string): Promise<any>;
add{{ properCase singularName }}(context: ActionContext<I{{ properCase singularName }}State, IState>, {{ camelCase singularName }}: I{{ properCase singularName }}): Promise<any>;
update{{ properCase singularName }}(context: ActionContext<I{{ properCase singularName }}State, IState>, {{ camelCase singularName }}: I{{ properCase singularName }}): Promise<any>;
delete{{ properCase singularName }}(context: ActionContext<I{{ properCase singularName }}State, IState>, {{ camelCase singularName }}: I{{ properCase singularName }}): Promise<any>;
}

export const {{ properCase singularName }}Actions: I{{ properCase singularName }}Actions = {
async fetch{{ properCase pluralName }}({ commit }) {
try {
const response = await HttpService.get<I{{ properCase singularName }}[]>('/{{ singularName }}');
commit('SET_{{ constantCase pluralName }}', response.data);
} catch (e) {
throw e;
}
},
async fetch{{ properCase singularName }}({ commit }, id) {
try {
const response = await HttpService.get<I{{ properCase singularName }}>(`/{{ singularName }}/${ id }`);
commit('SET_CURRENT_{{ constantCase singularName }}', response.data);
} catch (e) {
throw e;
}
},
async add{{ properCase singularName }}({ commit }, {{ camelCase singularName }}) {
try {
const response = await HttpService.post<I{{ properCase singularName }}>('/{{ singularName }}', {{ camelCase singularName }});
commit('ADD_{{ constantCase singularName }}', response.data);
} catch (e) {
throw e;
}
},
async update{{ properCase singularName }}({ commit }, {{ camelCase singularName }}) {
try {
const response = await HttpService.put<I{{ properCase singularName }}>(`/{{ singularName }}/${ {{ camelCase singularName }}.id }`, {{ camelCase singularName }});
commit('UPDATE_{{ constantCase singularName }}', response.data);
} catch (e) {
throw e;
}
},
async delete{{ properCase singularName }}({ commit }, {{ camelCase singularName }}) {
try {
await HttpService.delete<I{{ properCase singularName }}>(`/{{ singularName }}/${ {{ camelCase singularName }}.id }`);
commit('DELETE_{{ constantCase singularName }}', {{ camelCase singularName }});
} catch (e) {
throw e;
}
},
};
18 changes: 18 additions & 0 deletions .vuesion/generators/crud-module/getters.spec.ts.hbs
@@ -0,0 +1,18 @@
import { {{ properCase singularName }}Getters } from './getters';
import { {{ properCase singularName }}DefaultState, I{{ properCase singularName }}State } from './state';

describe('{{ properCase singularName }}Getters', () => {
let testState: I{{ properCase singularName }}State;

beforeEach(() => {
testState = {{ properCase singularName }}DefaultState();
});

test('it should get the {{ camelCase pluralName }}', () => {
expect({{ properCase singularName }}Getters.{{ camelCase pluralName }}(testState)).toEqual(testState.{{ camelCase pluralName }});
});

test('it should get the {{ camelCase pluralName }}', () => {
expect({{ properCase singularName }}Getters.current{{ properCase singularName }}(testState)).toEqual(testState.current{{ properCase singularName }});
});
});
16 changes: 16 additions & 0 deletions .vuesion/generators/crud-module/getters.ts.hbs
@@ -0,0 +1,16 @@
import { I{{ properCase singularName }}State } from './state';
import { I{{ properCase singularName }} } from './I{{ properCase singularName }}';

export interface I{{ properCase singularName }}Getters {
{{ camelCase pluralName }}(state: I{{ properCase singularName }}State): I{{ properCase singularName }}[];
current{{ properCase singularName }}(state: I{{ properCase singularName }}State): I{{ properCase singularName }};
}

export const {{ properCase singularName }}Getters: I{{ properCase singularName }}Getters = {
{{ camelCase pluralName }}(state) {
return state.{{ camelCase pluralName }};
},
current{{ properCase singularName }}(state) {
return state.current{{ properCase singularName }};
},
};
6 changes: 6 additions & 0 deletions .vuesion/generators/crud-module/interface.ts.hbs
@@ -0,0 +1,6 @@
export interface I{{properCase singularName}} {
/**
* define your data structure here
*/
id?: string;
}
22 changes: 22 additions & 0 deletions .vuesion/generators/crud-module/module.ts.hbs
@@ -0,0 +1,22 @@
import { Module } from 'vuex';
import { IState } from '@/app/state';
import { {{ properCase singularName }}DefaultState, I{{ properCase singularName }}State } from './state';
import { {{ properCase singularName }}Actions } from './actions';
import { {{ properCase singularName }}Getters } from './getters';
import { {{ properCase singularName }}Mutations } from './mutations';

export const {{ properCase singularName }}Module: Module<I{{ properCase singularName }}State, IState> = {
namespaced: true,
actions: {
...{{ properCase singularName }}Actions,
},
getters: {
...{{ properCase singularName }}Getters,
},
state: {
...{{ properCase singularName }}DefaultState(),
},
mutations: {
...{{ properCase singularName }}Mutations,
},
};

0 comments on commit 05ee8d9

Please sign in to comment.