Add fields store (#144)

* Add fields store

* Add test coverage for fields store

* Remove hydration tests

It doesn't do anything itself, but just calls init / reset methods of stores

* Rename store methods to hydrate / dehydrate

* DRY that sucker

* Move hydration logic into a store

* Fix tests for new store

* Rename hydrate store to app store, fix tests in auth

* Fix tests of router

* Fix tests in module-bar-logo

* bunch of things

* Fix tests in hydrate

* Fix router tests

* Clean up auth tests

* Update tests for collections / fields stores

* Use stores instead of mocks in tests

* Add test for store getter in collections
This commit is contained in:
Rijk van Zanten
2020-03-11 10:36:39 -04:00
committed by GitHub
parent 74c99a55b6
commit a2ba2c8783
18 changed files with 709 additions and 131 deletions

10
src/stores/app/app.ts Normal file
View File

@@ -0,0 +1,10 @@
import { createStore } from 'pinia';
export const useAppStore = createStore({
id: 'app',
state: () => ({
hydrated: false,
hydrating: false,
error: null
})
});

4
src/stores/app/index.ts Normal file
View File

@@ -0,0 +1,4 @@
import { useAppStore } from './app';
export { useAppStore };
export default useAppStore;

View File

@@ -0,0 +1,179 @@
import api from '@/api';
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
import formatTitle from '@directus/format-title';
import i18n from '@/lang';
import { useProjectsStore } from '@/stores/projects';
import { useCollectionsStore } from './collections';
jest.mock('@directus/format-title');
jest.mock('@/api');
jest.mock('@/lang');
describe('Stores / collections', () => {
let req: any = {};
beforeAll(() => {
Vue.config.productionTip = false;
Vue.config.devtools = false;
Vue.use(VueCompositionAPI);
});
beforeEach(() => {
req = {};
});
describe('Getters / visibleCollections', () => {
it('Filters collections starting with directus_', () => {
const collectionsStore = useCollectionsStore(req);
collectionsStore.state.collections = [
{
collection: 'test-1'
},
{
collection: 'test-2'
},
{
collection: 'directus_test'
},
{
collection: 'test-3'
}
] as any;
expect(collectionsStore.visibleCollections).toEqual([
{
collection: 'test-1'
},
{
collection: 'test-2'
},
{
collection: 'test-3'
}
]);
});
it('Filters collections that have the hidden flag true', () => {
const collectionsStore = useCollectionsStore(req);
collectionsStore.state.collections = [
{
collection: 'test-1',
hidden: true
},
{
collection: 'test-2',
hidden: false
},
{
collection: 'test-3',
hidden: null
}
] as any;
expect(collectionsStore.visibleCollections).toEqual([
{
collection: 'test-2'
},
{
collection: 'test-3'
}
]);
});
});
describe('Actions / Hydrate', () => {
it('Calls the right endpoint', async () => {
(api.get as jest.Mock).mockImplementation(() =>
Promise.resolve({
data: {
data: []
}
})
);
const projectsStore = useProjectsStore(req);
const collectionsStore = useCollectionsStore(req);
projectsStore.state.currentProjectKey = 'my-project';
await collectionsStore.hydrate();
expect(api.get).toHaveBeenCalledWith('/my-project/collections');
});
it('Formats the title to use as name', async () => {
(api.get as jest.Mock).mockImplementation(() =>
Promise.resolve({
data: {
data: [
{
collection: 'test_collection'
}
]
}
})
);
const projectsStore = useProjectsStore(req);
const collectionsStore = useCollectionsStore(req);
projectsStore.state.currentProjectKey = 'my-project';
await collectionsStore.hydrate();
expect(formatTitle).toHaveBeenCalledWith('test_collection');
expect(collectionsStore.state.collections[0].hasOwnProperty('name')).toBe(true);
});
it('Registers the passed translations to i18n to be registered', async () => {
(api.get as jest.Mock).mockImplementation(() =>
Promise.resolve({
data: {
data: [
{
collection: 'test_collection',
translation: [
{
locale: 'en-US',
translation: 'Test collection'
},
{
locale: 'nl-NL',
translation: 'Test verzameling'
}
]
}
]
}
})
);
const projectsStore = useProjectsStore(req);
projectsStore.state.currentProjectKey = 'my-project';
const collectionsStore = useCollectionsStore(req);
await collectionsStore.hydrate();
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('en-US', {
collections: {
test_collection: 'Test collection'
}
});
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('nl-NL', {
collections: {
test_collection: 'Test verzameling'
}
});
});
});
describe('Actions/ Dehydrate', () => {
it('Calls reset on dehydrate', async () => {
const collectionsStore = useCollectionsStore(req);
jest.spyOn(collectionsStore, 'reset');
await collectionsStore.dehydrate();
expect(collectionsStore.reset).toHaveBeenCalled();
});
});
});

View File

@@ -1,7 +1,7 @@
import { createStore } from 'pinia';
import api from '@/api';
import { Collection, CollectionRaw } from './types';
import useProjectsStore from '@/stores/projects';
import { useProjectsStore } from '@/stores/projects';
import i18n from '@/lang/';
import { notEmpty } from '@/utils/is-empty';
import VueI18n from 'vue-i18n';
@@ -20,9 +20,9 @@ export const useCollectionsStore = createStore({
}
},
actions: {
async getCollections() {
async hydrate() {
const projectsStore = useProjectsStore();
const { currentProjectKey } = projectsStore.state;
const currentProjectKey = projectsStore.state.currentProjectKey;
const response = await api.get(`/${currentProjectKey}/collections`);
@@ -54,6 +54,9 @@ export const useCollectionsStore = createStore({
icon
};
});
},
async dehydrate() {
this.reset();
}
}
});

View File

@@ -0,0 +1,120 @@
import api from '@/api';
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
import formatTitle from '@directus/format-title';
import i18n from '@/lang';
import { useProjectsStore } from '@/stores/projects';
import { useFieldsStore } from './fields';
jest.mock('@directus/format-title');
jest.mock('@/api');
jest.mock('@/lang');
describe('Stores / Fields', () => {
let req: any = {};
beforeAll(() => {
Vue.config.productionTip = false;
Vue.config.devtools = false;
Vue.use(VueCompositionAPI);
});
beforeEach(() => {
req = {};
});
describe('Hydrate', () => {
it('Calls the right endpoint', () => {
(api.get as jest.Mock).mockImplementation(() =>
Promise.resolve({
data: {
data: []
}
})
);
const projectsStore = useProjectsStore(req);
projectsStore.state.currentProjectKey = 'my-project';
const fieldsStore = useFieldsStore(req);
fieldsStore.hydrate().then(() => {
expect(api.get).toHaveBeenCalledWith('/my-project/fields');
});
});
it('Formats the title to use as name', async () => {
(api.get as jest.Mock).mockImplementation(() =>
Promise.resolve({
data: {
data: [
{
field: 'test_field'
}
]
}
})
);
const projectsStore = useProjectsStore(req);
projectsStore.state.currentProjectKey = 'my-project';
const fieldsStore = useFieldsStore(req);
await fieldsStore.hydrate();
expect(formatTitle).toHaveBeenCalledWith('test_field');
expect(fieldsStore.state.fields[0].hasOwnProperty('name')).toBe(true);
});
it('Registers the passed translations to i18n to be registered', async () => {
(api.get as jest.Mock).mockImplementation(() =>
Promise.resolve({
data: {
data: [
{
field: 'test_field',
translation: [
{
locale: 'en-US',
translation: 'Test field'
},
{
locale: 'nl-NL',
translation: 'Test veld'
}
]
}
]
}
})
);
const projectsStore = useProjectsStore(req);
projectsStore.state.currentProjectKey = 'my-project';
const fieldsStore = useFieldsStore(req);
await fieldsStore.hydrate();
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('en-US', {
fields: {
test_field: 'Test field'
}
});
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('nl-NL', {
fields: {
test_field: 'Test veld'
}
});
});
});
describe('Dehydrate', () => {
it('Calls reset on dehydrate', async () => {
const fieldsStore: any = useFieldsStore(req);
jest.spyOn(fieldsStore, 'reset');
await fieldsStore.dehydrate();
expect(fieldsStore.reset).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,53 @@
import { createStore } from 'pinia';
import { FieldRaw, Field } from './types';
import api from '@/api';
import { useProjectsStore } from '@/stores/projects';
import VueI18n from 'vue-i18n';
import { notEmpty } from '@/utils/is-empty';
import { i18n } from '@/lang';
import formatTitle from '@directus/format-title';
export const useFieldsStore = createStore({
id: 'fields',
state: () => ({
fields: [] as Field[]
}),
actions: {
async hydrate() {
const projectsStore = useProjectsStore();
const currentProjectKey = projectsStore.state.currentProjectKey;
const response = await api.get(`/${currentProjectKey}/fields`);
const fields: FieldRaw[] = response.data.data;
this.state.fields = fields.map(field => {
let name: string | VueI18n.TranslateResult;
if (notEmpty(field.translation)) {
for (let i = 0; i < field.translation.length; i++) {
const { locale, translation } = field.translation[i];
i18n.mergeLocaleMessage(locale, {
fields: {
[field.field]: translation
}
});
}
name = i18n.t(`fields.${field.field}`);
} else {
name = formatTitle(field.field);
}
return {
...field,
name
};
});
},
async dehydrate() {
this.reset();
}
}
});

View File

@@ -0,0 +1,4 @@
import { useFieldsStore } from './fields';
export { useFieldsStore };
export default useFieldsStore;

View File

@@ -0,0 +1,39 @@
import VueI18n from 'vue-i18n';
type Translation = {
locale: string;
translation: string;
};
type Width = 'half' | 'half-left' | 'half-right' | 'full' | 'fill';
export interface FieldRaw {
id: number;
collection: string;
field: string;
datatype: string;
unique: boolean;
primary_key: boolean;
auto_increment: boolean;
default_value: any; // eslint-disable-line @typescript-eslint/no-explicit-any
note: string;
signed: boolean;
type: string;
sort: null | number;
interface: string;
hidden_detail: boolean;
hidden_browse: boolean;
required: boolean;
options: null | { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any
locked: boolean;
translation: null | Translation[];
readonly: boolean;
width: null | Width;
validaton: string;
group: number;
length: string | number;
}
export interface Field extends FieldRaw {
name: string | VueI18n.TranslateResult;
}

View File

@@ -8,10 +8,6 @@ describe('Stores / Projects', () => {
Vue.use(VueCompositionAPI);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Getters / currentProject', () => {
const dummyProject = {
key: 'my-project',

View File

@@ -10,9 +10,6 @@ export const useRequestsStore = createStore({
queueHasItems: state => state.queue.length > 0
},
actions: {
reset() {
this.state.queue = [];
},
startRequest() {
const id = nanoid();
this.state.queue = [...this.state.queue, id];