import fetchMock from 'fetch-mock'; import { cloneDeep } from 'lodash'; import { Module } from 'vuex'; import { describe, expect, it, vi } from 'vitest'; import RootState from '../types/RootStateModel'; import { CmsStore, storeConfig } from '../index'; import { actions as crudActions, mutations as crudMutations, state as crudState, getters as crudGetters } from './crud-store'; import CrudStoreModel, { CrudStoreItemsModel, GetItemByIdPayload, GetSingletonItemPayload } from '../types/CrudStoreModel'; import { CrudSearchReponse, ITEMS_PER_PAGE } from '../../api/crud-api'; import { SearchCrudItemPayload } from '../../types/api'; const sampleCrudItem: CrudStoreItemsModel = { $type: 'Q.Cms.SampleCms.Domain.Items.Product, Q.Cms.SampleCms.Domain', name: 'Fromage', amount: 4, expirationDate: '2019-12-31T08:57:20.534718+01:00', translationSetId: '992e333d-52e2-44c8-b237-cdcca3fbeb0a', language: 'fr-FR', id: '123', created: '2019-12-31T08:57:20.534714+01:00', modified: '2019-12-31T08:57:20.534886+01:00', hasDynamicValueTags: false }; const sampleCrudItemUpdated: CrudStoreItemsModel = { ...sampleCrudItem }; sampleCrudItemUpdated.amount = 5; const sampleCrudItems: CrudStoreItemsModel[] = [ { $type: 'Q.Cms.SampleCms.Domain.Items.Product, Q.Cms.SampleCms.Domain', name: 'Fromage', amount: 4, expirationDate: '2019-12-31T08:57:20.534718+01:00', translationSetId: '992e333d-52e2-44c8-b237-cdcca3fbeb0a', language: 'fr-FR', id: '456', created: '2019-12-31T08:57:20.534714+01:00', modified: '2019-12-31T08:57:20.534886+01:00', hasDynamicValueTags: false }, { $type: 'Q.Cms.SampleCms.Domain.Items.Product, Q.Cms.SampleCms.Domain', name: 'Fromage', amount: 4, expirationDate: '2019-12-31T08:57:20.534718+01:00', translationSetId: '992e333d-52e2-44c8-b237-cdcca3fbeb0a', language: 'fr-FR', id: '789', created: '2019-12-31T08:57:20.534714+01:00', modified: '2019-12-31T08:57:20.534886+01:00', hasDynamicValueTags: false } ]; const emptySearchResult: CrudSearchReponse = { $type: 'Q.Cms.Core.Services.Models.PaginatedSearchResult`1[[Q.Cms.Core.Domain.CmsItem, Q.Cms.Core.Domain]], Q.Cms.Core.Services', items: [], currentPage: 1, totalPages: 1, fromRecord: 1, toRecord: 1, totalQueryItems: 1, hasMoreResults: false, totalItemsEntireSet: 1 }; const searchResult: CrudSearchReponse = { $type: 'Q.Cms.Core.Services.Models.PaginatedSearchResult`1[[Q.Cms.Core.Domain.CmsItem, Q.Cms.Core.Domain]], Q.Cms.Core.Services', items: sampleCrudItems, currentPage: 1, totalPages: 1, fromRecord: 1, toRecord: 1, totalQueryItems: 1, hasMoreResults: false, totalItemsEntireSet: 1 }; const searchResultMultiplePages: CrudSearchReponse = { $type: 'Q.Cms.Core.Services.Models.PaginatedSearchResult`1[[Q.Cms.Core.Domain.CmsItem, Q.Cms.Core.Domain]], Q.Cms.Core.Services', items: sampleCrudItems, currentPage: 2, totalPages: 4, fromRecord: 1, toRecord: 1, totalQueryItems: 100, hasMoreResults: false, totalItemsEntireSet: 1 }; describe('Crud store', () => { afterEach(() => { fetchMock.restore(); }); function createLocalStore(hasInitialState?: boolean): CmsStore { const store = new CmsStore(cloneDeep(storeConfig)); const clonedCrudState = cloneDeep(crudState); clonedCrudState.path = 'teststore'; if (hasInitialState) { clonedCrudState.itemsCache = [sampleCrudItem]; } store.registerModule( 'teststore', cloneDeep({ namespaced: true, state: clonedCrudState, getters: crudGetters, actions: crudActions, mutations: crudMutations }) as Module ); return store; } it('should fetch schema on store setup', () => { const store = createLocalStore(); store.dispatch = vi.fn(); expect(store.dispatch).not.toHaveBeenCalled(); store.initialize(); expect(store.dispatch).toHaveBeenCalledWith('teststore/fetchCrudSchema', undefined); }); it('should fetch by id and put them in a store', async () => { fetchMock.get('/api/items/teststore/123', { status: 200, body: sampleCrudItem }); const store = createLocalStore(); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([]); const item = await store.dispatch('teststore/getItemById', { id: '123' }); expect(item).toEqual(sampleCrudItem); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItem]); }); it('should not fetch by id when id is already present in store', async () => { const store = createLocalStore(true); store.commit = vi.fn(); expect(store.commit).not.toHaveBeenCalled(); await store.dispatch('teststore/getItemById', { id: '123' }); expect(store.commit).not.toHaveBeenCalled(); }); it('should fetch by id when id is already present in store, but ignoreCache is used', async () => { fetchMock.get('/api/items/teststore/123', { status: 200, body: sampleCrudItemUpdated }); const store = createLocalStore(true); store.commit = vi.fn(); expect(store.commit).not.toHaveBeenCalled(); const payload1: GetItemByIdPayload = { id: '123' }; const itemFromCache = await store.dispatch('teststore/getItemById', payload1); expect(itemFromCache.amount).toEqual(4); const payload2: GetItemByIdPayload = { id: '123', ignoreCache: true }; const itemIgnoredCache = await store.dispatch('teststore/getItemById', payload2); expect(store.commit).toHaveBeenCalled(); expect(itemIgnoredCache.amount).toEqual(5); }); it('should fetch multiple items and put them in a store', async () => { fetchMock.post('/api/items/teststore/ids', { status: 200, body: sampleCrudItems }); const store = createLocalStore(); await store.dispatch('teststore/getItemByIds', ['456', '789']); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual(sampleCrudItems); }); it('should not fetch item in multiple items which is already present in store', async () => { fetchMock.post('/api/items/teststore/ids', { status: 200, body: sampleCrudItems }); const store = createLocalStore(true); store.commit = vi.fn(); expect(store.commit).not.toHaveBeenCalled(); await store.dispatch('teststore/getItemByIds', ['123', '456', '789']); expect(store.commit).toHaveBeenCalledWith('teststore/addOrUpdateItemsToCache', sampleCrudItems, undefined); }); it('should not fetch when all multiple items are already present in store', async () => { const store = createLocalStore(true); store.commit = vi.fn(); expect(store.commit).not.toHaveBeenCalled(); await store.dispatch('teststore/getItemByIds', ['123']); expect(store.commit).not.toHaveBeenCalled(); }); it('should update an item and also update the stored item', async () => { fetchMock.put('/api/items/teststore/123', { status: 200, body: sampleCrudItemUpdated }); const store = createLocalStore(true); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItem]); await store.dispatch('teststore/saveItem', { id: '123', item: {} }); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItemUpdated]); }); it('should correctly create the request url based on the requested publish action', async () => { const publishItemPath = '/api/items/teststore/123/publish'; const depublishItemPath = '/api/items/teststore/123/depublish'; fetchMock.put(publishItemPath, { status: 200, body: sampleCrudItemUpdated }); fetchMock.put(depublishItemPath, { status: 200, body: sampleCrudItemUpdated }); const store = createLocalStore(true); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItem]); expect(fetchMock.called(publishItemPath)).toEqual(false); expect(fetchMock.called(depublishItemPath)).toEqual(false); await store.dispatch('teststore/publishItem', { id: '123', item: {}, action: 'publish' }); expect(fetchMock.called(publishItemPath)).toEqual(true); expect(fetchMock.called(depublishItemPath)).toEqual(false); await store.dispatch('teststore/publishItem', { id: '123', item: {}, action: 'depublish' }); expect(fetchMock.called(publishItemPath)).toEqual(true); expect(fetchMock.called(depublishItemPath)).toEqual(true); }); it('should publish an item and also update the stored item', async () => { fetchMock.put('/api/items/teststore/123/publish', { status: 200, body: sampleCrudItemUpdated }); const store = createLocalStore(true); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItem]); await store.dispatch('teststore/publishItem', { id: '123', item: {}, action: 'publish' }); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItemUpdated]); }); it('should delete an item', async () => { const deleteItemPath = '/api/items/teststore/123'; const fetchItemsPath = /api\/items\/teststore\/search/; fetchMock.delete(deleteItemPath, { status: 200, body: sampleCrudItemUpdated }); fetchMock.get(fetchItemsPath, { status: 200, body: emptySearchResult }); const store = createLocalStore(true); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItem]); await store.dispatch('teststore/deleteItem', { id: '123', item: {} }); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([]); expect(fetchMock.called(deleteItemPath)).toEqual(true); expect(fetchMock.called(fetchItemsPath)).toEqual(true); }); it('should create an item', async () => { const createItemPath = '/api/items/teststore'; const fetchItemsPath = /api\/items\/teststore\/search/; fetchMock.post(createItemPath, { status: 200, body: sampleCrudItem }); fetchMock.get(fetchItemsPath, { status: 200, body: emptySearchResult }); const store = createLocalStore(); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([]); await store.dispatch('teststore/saveItem', { item: {} }); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([sampleCrudItem]); expect(fetchMock.called(createItemPath)).toEqual(true); expect(fetchMock.called(fetchItemsPath)).toEqual(true); }); it('should get items based on empty search query and store them', async () => { const fetchItemsPath = /api\/items\/teststore\/search/; fetchMock.get(fetchItemsPath, { status: 200, body: searchResult }); const store = createLocalStore(); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([]); await store.dispatch('teststore/fetchItems'); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual(sampleCrudItems); expect(store.getters['teststore/result']()).toEqual(searchResult); expect(fetchMock.called(fetchItemsPath)).toEqual(true); }); it('should get items based on filled search query and store them', async () => { const path = `/api/items/teststore/search?query=foo&page=2&itemsPerPage=${ITEMS_PER_PAGE}&language=&sort=`; fetchMock.get(`/api/items/teststore/search?query=foo&page=1&itemsPerPage=${ITEMS_PER_PAGE}&language==&sort=`, { status: 200, body: emptySearchResult }); fetchMock.get(path, { status: 200, body: searchResult }); const store = createLocalStore(); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual([]); const query: SearchCrudItemPayload = { query: 'foo', page: 2 }; await store.dispatch('teststore/fetchItems', query); // @ts-ignore expect(store.state.teststore.itemsCache).toEqual(sampleCrudItems); expect(store.getters['teststore/result']()).toEqual(searchResult); expect(fetchMock.called(path)).toEqual(true); }); it('should get correct pagination status (1)', async () => { const path = `/api/items/teststore/search?query=foo&page=2&itemsPerPage=${ITEMS_PER_PAGE}&language=&sort=`; fetchMock.get(path, { status: 200, body: searchResult }); const store = createLocalStore(); const query: SearchCrudItemPayload = { query: 'foo', page: 2 }; await store.dispatch('teststore/fetchItems', query); expect(store.getters['teststore/nextDisabled']()).toEqual(true); expect(store.getters['teststore/previousDisabled']()).toEqual(true); }); it('should get correct pagination status (2)', async () => { const path = `/api/items/teststore/search?query=foo&page=2&itemsPerPage=${ITEMS_PER_PAGE}&language=&sort=`; fetchMock.get(path, { status: 200, body: searchResultMultiplePages }); const store = createLocalStore(); const query: SearchCrudItemPayload = { query: 'foo', page: 2 }; await store.dispatch('teststore/fetchItems', query); expect(store.getters['teststore/nextDisabled']()).toEqual(false); expect(store.getters['teststore/previousDisabled']()).toEqual(false); }); it('should get a singleton item when an id is present', async () => { fetchMock.get(`/api/items/teststore/${sampleCrudItems[0].id}`, { status: 200, body: sampleCrudItems[0] }); const store = createLocalStore(); const payload: GetSingletonItemPayload = { defaultLanguage: sampleCrudItems[0].language, id: sampleCrudItems[0].id }; const item = await store.dispatch('teststore/getSingletonItem', payload); expect(item.id).toEqual(sampleCrudItems[0].id); }); it('should get a singleton item when no id is present, but there are items in the defaultlanguage', async () => { const fetchItemsPath = /api\/items\/teststore\/search/; fetchMock.get(fetchItemsPath, { status: 200, body: searchResult }); const store = createLocalStore(); const payload: GetSingletonItemPayload = { defaultLanguage: sampleCrudItems[0].language }; const item = await store.dispatch('teststore/getSingletonItem', payload); expect(item.id).toEqual(sampleCrudItems[0].id); }); it('should get a singleton item when no id is present, but there are items but not in the defaultlanguage', async () => { const fetchItemsPath = /api\/items\/teststore\/search/; fetchMock.get(fetchItemsPath, { status: 200, body: searchResult }); const store = createLocalStore(); const payload: GetSingletonItemPayload = { defaultLanguage: 'nl_NL' }; const item = await store.dispatch('teststore/getSingletonItem', payload); expect(item.id).toEqual(sampleCrudItems[0].id); }); it('should not get a singleton item when no id is present and there are no items', async () => { const fetchItemsPath = /api\/items\/teststore\/search/; fetchMock.get(fetchItemsPath, { status: 200, body: emptySearchResult }); const store = createLocalStore(); const payload: GetSingletonItemPayload = { defaultLanguage: 'nl_NL' }; const item = await store.dispatch('teststore/getSingletonItem', payload); expect(item).toEqual(undefined); }); });