From a8105ec27874e381dfda0a171dac0b72244e956b Mon Sep 17 00:00:00 2001 From: Alex Thirlwall Date: Tue, 7 May 2024 16:47:50 +0200 Subject: [PATCH] Add caching tests for when getCacheIdentifier is asynchronous --- .changeset/stupid-pandas-greet.md | 5 + packages/ajax/test/Ajax.test.js | 202 ++++++++++++++++++++---------- 2 files changed, 141 insertions(+), 66 deletions(-) create mode 100644 .changeset/stupid-pandas-greet.md diff --git a/.changeset/stupid-pandas-greet.md b/.changeset/stupid-pandas-greet.md new file mode 100644 index 000000000..caa28ad62 --- /dev/null +++ b/.changeset/stupid-pandas-greet.md @@ -0,0 +1,5 @@ +--- +'@lion/ajax': patch +--- + +Add caching tests for when getCacheIdentifier is asynchronous diff --git a/packages/ajax/test/Ajax.test.js b/packages/ajax/test/Ajax.test.js index 0c6049ed2..604b5111f 100644 --- a/packages/ajax/test/Ajax.test.js +++ b/packages/ajax/test/Ajax.test.js @@ -451,6 +451,8 @@ describe('Ajax', () => { let cacheId; /** @type {() => string} */ let getCacheIdentifier; + /** @type {() => Promise} */ + let getCacheIdentifierAsync; const newCacheId = () => { if (!cacheId) { @@ -463,6 +465,7 @@ describe('Ajax', () => { beforeEach(() => { getCacheIdentifier = () => String(cacheId); + getCacheIdentifierAsync = () => Promise.resolve(String(cacheId)); }); it('does not add cache interceptors when useCache is turned off', () => { @@ -503,7 +506,97 @@ describe('Ajax', () => { expect(customAjax._responseInterceptors.length).to.equal(1); }); - describe('Caching interceptors: synchronous getCacheIdentifier tests', async () => { + const cachingTests = {}; + cachingTests.works = async (/** @type {Ajax} */ customAjax) => { + await customAjax.fetch('/foo'); + + const secondResponse = await customAjax.fetch('/foo'); + expect(fetchStub.callCount).to.equal(1); + expect(await secondResponse.text()).to.equal('mock response'); + expect(secondResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); + expect(secondResponse.headers.get('Content-Type')).to.equal('application/json'); + }; + + cachingTests.resetOnCacheIDChange = async (/** @type {Ajax} */ customAjax) => { + /* Three calls to the same endpoint should result in a + single fetchStubCall due to caching */ + await customAjax.fetch('/foo'); + await customAjax.fetch('/foo'); + await customAjax.fetch('/foo'); + expect(fetchStub.callCount).to.equal(1); + + newCacheId(); + await customAjax.fetch('/foo'); + + /* The newCacheId call should reset the cache, thereby adding an + extra call to the fetchStub call count. */ + expect(fetchStub.callCount).to.equal(2); + }; + + cachingTests.completePendingOnCacheIDChange = async (/** @type {Ajax} */ customAjax) => { + const requestOne = customAjax.fetch('/foo').then(() => 'completedRequestOne'); + newCacheId(); + const requestTwo = customAjax.fetch('/foo').then(() => 'completedRequestTwo'); + expect(await requestOne).to.equal('completedRequestOne'); + expect(await requestTwo).to.equal('completedRequestTwo'); + }; + + cachingTests.worksWithFetchJson = async (/** @type {Ajax} */ customAjax) => { + fetchStub.returns(Promise.resolve(new Response('{"a":1,"b":2}', responseInit()))); + + const firstResponse = await customAjax.fetchJson('/foo'); + expect(firstResponse.body).to.deep.equal({ a: 1, b: 2 }); + expect(firstResponse.response.headers.get('X-Custom-Header')).to.equal('y-custom-value'); + expect(firstResponse.response.headers.get('Content-Type')).to.equal('application/json'); + + const secondResponse = await customAjax.fetchJson('/foo'); + expect(fetchStub.callCount).to.equal(1); + expect(secondResponse.body).to.deep.equal({ a: 1, b: 2 }); + expect(secondResponse.response.headers.get('X-Custom-Header')).to.equal('y-custom-value'); + expect(secondResponse.response.headers.get('Content-Type')).to.equal('application/json'); + }; + + cachingTests.invalidatesOnNonGetMethod = async (/** @type {Ajax} */ customAjax) => { + await customAjax.fetch('/foo'); + + const secondResponse = await customAjax.fetch('/foo', { method: 'POST' }); + expect(fetchStub.callCount).to.equal(2); + expect(await secondResponse.text()).to.equal('mock response'); + expect(secondResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); + expect(secondResponse.headers.get('Content-Type')).to.equal('application/json'); + + const thirdResponse = await customAjax.fetch('/foo'); + expect(fetchStub.callCount).to.equal(3); + expect(await thirdResponse.text()).to.equal('mock response'); + expect(thirdResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); + expect(thirdResponse.headers.get('Content-Type')).to.equal('application/json'); + }; + + cachingTests.invalidatesOnTTLPassed = async (/** @type {Ajax} */ customAjax) => { + const clock = useFakeTimers({ + shouldAdvanceTime: true, + }); + + await customAjax.fetch('/foo'); + + const secondResponse = await customAjax.fetch('/foo'); + expect(fetchStub.callCount).to.equal(1); + expect(await secondResponse.text()).to.equal('mock response'); + expect(secondResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); + expect(secondResponse.headers.get('Content-Type')).to.equal('application/json'); + + clock.tick(101); + + const thirdResponse = await customAjax.fetch('/foo'); + expect(fetchStub.callCount).to.equal(2); + expect(await thirdResponse.text()).to.equal('mock response'); + expect(thirdResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); + expect(thirdResponse.headers.get('Content-Type')).to.equal('application/json'); + + clock.restore(); + }; + + describe('Caching interceptors: cache tests with synchronous getCacheIdentifier', async () => { /** * @type {Ajax} */ @@ -521,92 +614,69 @@ describe('Ajax', () => { }); it('works', async () => { - await customAjax.fetch('/foo'); - - const secondResponse = await customAjax.fetch('/foo'); - expect(fetchStub.callCount).to.equal(1); - expect(await secondResponse.text()).to.equal('mock response'); - expect(secondResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); - expect(secondResponse.headers.get('Content-Type')).to.equal('application/json'); + await cachingTests.works(customAjax); }); it('resets the cache if the cache ID changes', async () => { - /* Three calls to the same endpoint should result in a - single fetchStubCall due to caching */ - await customAjax.fetch('/foo'); - await customAjax.fetch('/foo'); - await customAjax.fetch('/foo'); - expect(fetchStub.callCount).to.equal(1); - - newCacheId(); - await customAjax.fetch('/foo'); - - /* The newCacheId call should reset the cache, thereby adding an - extra call to the fetchStub call count. */ - expect(fetchStub.callCount).to.equal(2); + await cachingTests.resetOnCacheIDChange(customAjax); }); it('Completes pending requests when the cache ID changes', async () => { - const requestOne = customAjax.fetch('/foo').then(() => 'completedRequestOne'); - newCacheId(); - const requestTwo = customAjax.fetch('/foo').then(() => 'completedRequestTwo'); - expect(await requestOne).to.equal('completedRequestOne'); - expect(await requestTwo).to.equal('completedRequestTwo'); + await cachingTests.completePendingOnCacheIDChange(customAjax); }); it('works with fetchJson', async () => { - fetchStub.returns(Promise.resolve(new Response('{"a":1,"b":2}', responseInit()))); - - const firstResponse = await customAjax.fetchJson('/foo'); - expect(firstResponse.body).to.deep.equal({ a: 1, b: 2 }); - expect(firstResponse.response.headers.get('X-Custom-Header')).to.equal('y-custom-value'); - expect(firstResponse.response.headers.get('Content-Type')).to.equal('application/json'); - - const secondResponse = await customAjax.fetchJson('/foo'); - expect(fetchStub.callCount).to.equal(1); - expect(secondResponse.body).to.deep.equal({ a: 1, b: 2 }); - expect(secondResponse.response.headers.get('X-Custom-Header')).to.equal('y-custom-value'); - expect(secondResponse.response.headers.get('Content-Type')).to.equal('application/json'); + await cachingTests.worksWithFetchJson(customAjax); }); it('is invalidated on non-get method', async () => { - await customAjax.fetch('/foo'); - - const secondResponse = await customAjax.fetch('/foo', { method: 'POST' }); - expect(fetchStub.callCount).to.equal(2); - expect(await secondResponse.text()).to.equal('mock response'); - expect(secondResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); - expect(secondResponse.headers.get('Content-Type')).to.equal('application/json'); - - const thirdResponse = await customAjax.fetch('/foo'); - expect(fetchStub.callCount).to.equal(3); - expect(await thirdResponse.text()).to.equal('mock response'); - expect(thirdResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); - expect(thirdResponse.headers.get('Content-Type')).to.equal('application/json'); + await cachingTests.invalidatesOnNonGetMethod(customAjax); }); it('is invalidated after TTL has passed', async () => { - const clock = useFakeTimers({ - shouldAdvanceTime: true, + await cachingTests.invalidatesOnTTLPassed(customAjax); + }); + }); + + describe('Caching interceptors: cache tests with asynchronous getCacheIdentifier', async () => { + /** + * @type {Ajax} + */ + let customAjax; + + beforeEach(async () => { + newCacheId(); + customAjax = new Ajax({ + cacheOptions: { + useCache: true, + maxAge: 100, + getCacheIdentifier: getCacheIdentifierAsync, + }, }); + }); - await customAjax.fetch('/foo'); + it('works', async () => { + await cachingTests.works(customAjax); + }); - const secondResponse = await customAjax.fetch('/foo'); - expect(fetchStub.callCount).to.equal(1); - expect(await secondResponse.text()).to.equal('mock response'); - expect(secondResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); - expect(secondResponse.headers.get('Content-Type')).to.equal('application/json'); + it('resets the cache if the cache ID changes', async () => { + await cachingTests.resetOnCacheIDChange(customAjax); + }); - clock.tick(101); + it('Completes pending requests when the cache ID changes', async () => { + await cachingTests.completePendingOnCacheIDChange(customAjax); + }); - const thirdResponse = await customAjax.fetch('/foo'); - expect(fetchStub.callCount).to.equal(2); - expect(await thirdResponse.text()).to.equal('mock response'); - expect(thirdResponse.headers.get('X-Custom-Header')).to.equal('y-custom-value'); - expect(thirdResponse.headers.get('Content-Type')).to.equal('application/json'); + it('works with fetchJson', async () => { + await cachingTests.worksWithFetchJson(customAjax); + }); - clock.restore(); + it('is invalidated on non-get method', async () => { + await cachingTests.invalidatesOnNonGetMethod(customAjax); + }); + + it('is invalidated after TTL has passed', async () => { + await cachingTests.invalidatesOnTTLPassed(customAjax); }); }); });