Loading src/DrupalState.ts +30 −11 Original line number Diff line number Diff line Loading @@ -486,19 +486,38 @@ class DrupalState { all = false, refresh = false, }: GetObjectParams): Promise<PartialState<State> | void> { if ( params !== undefined && typeof params !== 'string' && !(params instanceof DrupalJsonApiParams) ) { this.onError( new Error( `Invalid params: Params must be a string or instance of DrupalJsonApiParams (https://www.npmjs.com/package/drupal-jsonapi-params)` ) ); return; } const state = this.getState() as DsState; const paramString = typeof params === 'string' ? params : params?.getQueryString(); const collectionKey = paramString ? `${objectName}-${paramString}` : objectName; const resourceKey = `${objectName}Resources`; // Check for collection in the store const collectionState = state[objectName] as TJsonApiBodyDataRequired; const collectionState = state[collectionKey] as TJsonApiBodyDataRequired; // If an id is provided, find and return a resource if (id) { const resourceId = paramString ? `${id}-${paramString}` : id; const resourceState = !refresh ? (state[`${objectName}Resources`] as keyedResources) ? (state[resourceKey] as keyedResources) : false; // If requested resource is in the resource store, return that if (resourceState) { const resource = resourceState[id] as keyedResources; const resource = resourceState[resourceId] as keyedResources; if (resource) { !this.debug || console.log(`Matched resource ${id} in state`); return resource?.graphql Loading Loading @@ -559,24 +578,24 @@ class DrupalState { res )) as keyedResources; const objectResourceState = state[`${objectName}Resources`]; const objectResourceState = state[resourceKey]; if (objectResourceState) { // If the resource state exists, add the new resource to it. const updatedResourceState = { ...objectResourceState, [id]: resourceData, [resourceId]: resourceData, }; this.setState({ [`${objectName}Resources`]: updatedResourceState, [resourceKey]: updatedResourceState, }); } else { const newResourceState = { [id]: resourceData, [resourceId]: resourceData, }; this.setState({ [`${objectName}Resources`]: newResourceState }); this.setState({ [resourceKey]: newResourceState }); } return query Loading Loading @@ -630,7 +649,7 @@ class DrupalState { )) as keyedResources; const fetchedCollectionState = {} as CollectionState; fetchedCollectionState[objectName] = collectionData; fetchedCollectionState[collectionKey] = collectionData; this.setState(fetchedCollectionState); // if the all flag is present Loading Loading @@ -684,11 +703,11 @@ class DrupalState { const currentState = this.getState() as CollectionState; // using deepmerge to merge arrays instead of overwriting them const mergedCollection: keyedResources = deepmerge( currentState[objectName], currentState[collectionKey], nextPage ); currentState[objectName] = mergedCollection; currentState[collectionKey] = mergedCollection; this.setState(currentState); return nextPage.links as TJsonApiLinks; Loading src/__tests__/drupalState.test.ts +64 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ global.Headers = fetchMock.Headers; import { ServerResponse } from 'http'; import fetch from 'isomorphic-fetch'; import { DrupalJsonApiParams } from 'drupal-jsonapi-params'; import DrupalState from '../DrupalState'; Loading Loading @@ -153,6 +154,38 @@ describe('drupalState', () => { expect(fetchMock).toBeCalledTimes(2); }); test('Re-fetch resource if it exists in state but uses different parameters', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', apiPrefix: 'jsonapi', debug: true, }); store.setState({ dsApiIndex: indexResponse.links }); fetchMock.mock( 'https://dev-ds-demo.pantheonsite.io/en/jsonapi/node/recipe/33386d32-a87c-44b9-b66b-3dd0bfc38dca?filter%5Bstatus%5D=1', { status: 200, body: recipesResourceData1, } ); expect( await store.getObject({ objectName: 'node--recipe', id: '33386d32-a87c-44b9-b66b-3dd0bfc38dca', }) ).toEqual(recipesResourceObject1); const params = new DrupalJsonApiParams(); params.addFilter('status', '1'); expect( await store.getObject({ objectName: 'node--recipe', id: '33386d32-a87c-44b9-b66b-3dd0bfc38dca', params, }) ).toEqual(recipesResourceObject1); expect(fetchMock).toBeCalledTimes(2); }); test('Add resource object to local resource state if resource state already exists', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', Loading Loading @@ -298,6 +331,37 @@ describe('drupalState', () => { expect(fetchMock).toBeCalledTimes(1); }); test('Re-fetch object if it exists in state but uses different parameters', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', apiPrefix: 'jsonapi', debug: true, }); store.setState({ dsApiIndex: indexResponse.links }); fetchMock.mock( 'https://dev-ds-demo.pantheonsite.io/en/jsonapi/node/recipe?filter%5Bstatus%5D=1', { status: 200, body: recipes, }, { overwriteRoutes: true } ); expect( await store.getObject({ objectName: 'node--recipe', }) ).toEqual(recipesCollectionObject1); const params = new DrupalJsonApiParams(); params.addFilter('status', '1'); expect( await store.getObject({ objectName: 'node--recipe', params, }) ).toEqual(recipesCollectionObject1); expect(fetchMock).toBeCalledTimes(2); }); test('Fetch resource with authentication', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', Loading Loading
src/DrupalState.ts +30 −11 Original line number Diff line number Diff line Loading @@ -486,19 +486,38 @@ class DrupalState { all = false, refresh = false, }: GetObjectParams): Promise<PartialState<State> | void> { if ( params !== undefined && typeof params !== 'string' && !(params instanceof DrupalJsonApiParams) ) { this.onError( new Error( `Invalid params: Params must be a string or instance of DrupalJsonApiParams (https://www.npmjs.com/package/drupal-jsonapi-params)` ) ); return; } const state = this.getState() as DsState; const paramString = typeof params === 'string' ? params : params?.getQueryString(); const collectionKey = paramString ? `${objectName}-${paramString}` : objectName; const resourceKey = `${objectName}Resources`; // Check for collection in the store const collectionState = state[objectName] as TJsonApiBodyDataRequired; const collectionState = state[collectionKey] as TJsonApiBodyDataRequired; // If an id is provided, find and return a resource if (id) { const resourceId = paramString ? `${id}-${paramString}` : id; const resourceState = !refresh ? (state[`${objectName}Resources`] as keyedResources) ? (state[resourceKey] as keyedResources) : false; // If requested resource is in the resource store, return that if (resourceState) { const resource = resourceState[id] as keyedResources; const resource = resourceState[resourceId] as keyedResources; if (resource) { !this.debug || console.log(`Matched resource ${id} in state`); return resource?.graphql Loading Loading @@ -559,24 +578,24 @@ class DrupalState { res )) as keyedResources; const objectResourceState = state[`${objectName}Resources`]; const objectResourceState = state[resourceKey]; if (objectResourceState) { // If the resource state exists, add the new resource to it. const updatedResourceState = { ...objectResourceState, [id]: resourceData, [resourceId]: resourceData, }; this.setState({ [`${objectName}Resources`]: updatedResourceState, [resourceKey]: updatedResourceState, }); } else { const newResourceState = { [id]: resourceData, [resourceId]: resourceData, }; this.setState({ [`${objectName}Resources`]: newResourceState }); this.setState({ [resourceKey]: newResourceState }); } return query Loading Loading @@ -630,7 +649,7 @@ class DrupalState { )) as keyedResources; const fetchedCollectionState = {} as CollectionState; fetchedCollectionState[objectName] = collectionData; fetchedCollectionState[collectionKey] = collectionData; this.setState(fetchedCollectionState); // if the all flag is present Loading Loading @@ -684,11 +703,11 @@ class DrupalState { const currentState = this.getState() as CollectionState; // using deepmerge to merge arrays instead of overwriting them const mergedCollection: keyedResources = deepmerge( currentState[objectName], currentState[collectionKey], nextPage ); currentState[objectName] = mergedCollection; currentState[collectionKey] = mergedCollection; this.setState(currentState); return nextPage.links as TJsonApiLinks; Loading
src/__tests__/drupalState.test.ts +64 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ global.Headers = fetchMock.Headers; import { ServerResponse } from 'http'; import fetch from 'isomorphic-fetch'; import { DrupalJsonApiParams } from 'drupal-jsonapi-params'; import DrupalState from '../DrupalState'; Loading Loading @@ -153,6 +154,38 @@ describe('drupalState', () => { expect(fetchMock).toBeCalledTimes(2); }); test('Re-fetch resource if it exists in state but uses different parameters', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', apiPrefix: 'jsonapi', debug: true, }); store.setState({ dsApiIndex: indexResponse.links }); fetchMock.mock( 'https://dev-ds-demo.pantheonsite.io/en/jsonapi/node/recipe/33386d32-a87c-44b9-b66b-3dd0bfc38dca?filter%5Bstatus%5D=1', { status: 200, body: recipesResourceData1, } ); expect( await store.getObject({ objectName: 'node--recipe', id: '33386d32-a87c-44b9-b66b-3dd0bfc38dca', }) ).toEqual(recipesResourceObject1); const params = new DrupalJsonApiParams(); params.addFilter('status', '1'); expect( await store.getObject({ objectName: 'node--recipe', id: '33386d32-a87c-44b9-b66b-3dd0bfc38dca', params, }) ).toEqual(recipesResourceObject1); expect(fetchMock).toBeCalledTimes(2); }); test('Add resource object to local resource state if resource state already exists', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', Loading Loading @@ -298,6 +331,37 @@ describe('drupalState', () => { expect(fetchMock).toBeCalledTimes(1); }); test('Re-fetch object if it exists in state but uses different parameters', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', apiPrefix: 'jsonapi', debug: true, }); store.setState({ dsApiIndex: indexResponse.links }); fetchMock.mock( 'https://dev-ds-demo.pantheonsite.io/en/jsonapi/node/recipe?filter%5Bstatus%5D=1', { status: 200, body: recipes, }, { overwriteRoutes: true } ); expect( await store.getObject({ objectName: 'node--recipe', }) ).toEqual(recipesCollectionObject1); const params = new DrupalJsonApiParams(); params.addFilter('status', '1'); expect( await store.getObject({ objectName: 'node--recipe', params, }) ).toEqual(recipesCollectionObject1); expect(fetchMock).toBeCalledTimes(2); }); test('Fetch resource with authentication', async () => { const store: DrupalState = new DrupalState({ apiBase: 'https://dev-ds-demo.pantheonsite.io', Loading