diff --git a/.eslintrc.js b/.eslintrc.js index b10f9c15bf..92aaa97964 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,15 +6,15 @@ module.exports = { project: ['./tsconfig.json'], }, plugins: [ - 'eslint-plugin-tsdoc', - 'eslint-plugin-import', - 'eslint-plugin-unused-imports' + 'tsdoc', + 'import', + 'unused-imports', ], extends: [ 'es/node', 'plugin:import/errors', 'plugin:import/warnings', - 'plugin:import/typescript' + 'plugin:import/typescript', ], settings: { 'import/resolver': { @@ -72,7 +72,7 @@ module.exports = { { selector: [ 'typeParameter' ], format: [ 'PascalCase' ], - prefix: [ 'T' ] + prefix: [ 'T' ], } ], @@ -81,12 +81,12 @@ module.exports = { 'import/order': ['error', { alphabetize: { order: 'asc', - caseInsensitive: true + caseInsensitive: true, } }], 'import/no-duplicates': 'error', 'import/no-extraneous-dependencies': 'error', 'no-duplicate-imports': 'off', // doesn't work with type imports - 'unused-imports/no-unused-imports-ts': 'error' + 'unused-imports/no-unused-imports-ts': 'error', }, }; diff --git a/package-lock.json b/package-lock.json index 652c680038..19f4ef577c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3418,6 +3418,15 @@ } } }, + "eslint-plugin-jest": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.0.tgz", + "integrity": "sha512-827YJ+E8B9PvXu/0eiVSNFfxxndbKv+qE/3GSMhdorCaeaOehtqHGX2YDW9B85TEOre9n/zscledkFW/KbnyGg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^4.0.1" + } + }, "eslint-plugin-mocha": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-8.0.0.tgz", diff --git a/package.json b/package.json index cd6412b686..400799d970 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "eslint-config-es": "^3.20.3", "eslint-import-resolver-typescript": "^2.3.0", "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jest": "^24.1.0", "eslint-plugin-tsdoc": "^0.2.7", "eslint-plugin-unused-imports": "^1.0.0", "husky": "^4.2.5", diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 64c1468db8..2822e110bc 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -1,7 +1,11 @@ module.exports = { - env: { - jest: true - }, + plugins: [ + 'jest', + ], + extends: [ + 'plugin:jest/recommended', + 'plugin:jest/style', + ], rules: { '@typescript-eslint/no-unsafe-assignment': 'off', 'unicorn/no-useless-undefined': 'off', diff --git a/test/unit/init/CliRunner.test.ts b/test/unit/init/CliRunner.test.ts index c55bea7a2c..e1a627a8ef 100644 --- a/test/unit/init/CliRunner.test.ts +++ b/test/unit/init/CliRunner.test.ts @@ -78,7 +78,7 @@ describe('CliRunner', (): void => { expect(calledInstantiateFromUrl).toBeTruthy(); expect(calledRegisterAvailableModuleResources).toBeTruthy(); - expect(mockSetup.setup).toBeCalledTimes(1); + expect(mockSetup.setup).toHaveBeenCalledTimes(1); }); it('Writes to stderr when an exception occurs.', async(): Promise => { diff --git a/test/unit/ldp/http/BasicResponseWriter.test.ts b/test/unit/ldp/http/BasicResponseWriter.test.ts index 6796533a97..9dcf5df26a 100644 --- a/test/unit/ldp/http/BasicResponseWriter.test.ts +++ b/test/unit/ldp/http/BasicResponseWriter.test.ts @@ -30,25 +30,28 @@ describe('A BasicResponseWriter', (): void => { expect(response._getHeaders()).toMatchObject({ location: 'path' }); }); - it('responds with a body if the description has a body.', async(done): Promise => { + it('responds with a body if the description has a body.', async(): Promise => { const body = { binary: true, data: streamifyArray([ ' .' ]), metadata: new RepresentationMetadata(), }; - response.on('end', (): void => { - expect(response._isEndCalled()).toBeTruthy(); - expect(response._getStatusCode()).toBe(200); - expect(response._getHeaders()).toMatchObject({ location: 'path' }); - expect(response._getData()).toEqual(' .'); - done(); + const end = new Promise((resolve): void => { + response.on('end', (): void => { + expect(response._isEndCalled()).toBeTruthy(); + expect(response._getStatusCode()).toBe(200); + expect(response._getHeaders()).toMatchObject({ location: 'path' }); + expect(response._getData()).toEqual(' .'); + resolve(); + }); }); await writer.handle({ response, result: { identifier: { path: 'path' }, body }}); + await end; }); - it('responds with a content-type if the metadata has it.', async(done): Promise => { + it('responds with a content-type if the metadata has it.', async(): Promise => { const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' }); const body = { binary: true, @@ -56,15 +59,18 @@ describe('A BasicResponseWriter', (): void => { metadata, }; - response.on('end', (): void => { - expect(response._isEndCalled()).toBeTruthy(); - expect(response._getStatusCode()).toBe(200); - expect(response._getHeaders()).toMatchObject({ location: 'path', 'content-type': 'text/turtle' }); - expect(response._getData()).toEqual(' .'); - done(); + const end = new Promise((resolve): void => { + response.on('end', (): void => { + expect(response._isEndCalled()).toBeTruthy(); + expect(response._getStatusCode()).toBe(200); + expect(response._getHeaders()).toMatchObject({ location: 'path', 'content-type': 'text/turtle' }); + expect(response._getData()).toEqual(' .'); + resolve(); + }); }); await writer.handle({ response, result: { identifier: { path: 'path' }, body }}); + await end; }); it('responds with 500 if an error if there is an error.', async(): Promise => { diff --git a/test/unit/ldp/http/metadata/BasicMetadataExtractor.test.ts b/test/unit/ldp/http/metadata/BasicMetadataExtractor.test.ts index 4c970acf30..be37a03937 100644 --- a/test/unit/ldp/http/metadata/BasicMetadataExtractor.test.ts +++ b/test/unit/ldp/http/metadata/BasicMetadataExtractor.test.ts @@ -21,7 +21,7 @@ class BasicParser implements MetadataParser { } } -describe(' A BasicMetadataExtractor', (): void => { +describe('A BasicMetadataExtractor', (): void => { const handler = new BasicMetadataExtractor([ new BasicParser('aa'), new BasicParser('bb'), diff --git a/test/unit/logging/LogUtil.test.ts b/test/unit/logging/LogUtil.test.ts index a1f115cb66..b5f5b4ae33 100644 --- a/test/unit/logging/LogUtil.test.ts +++ b/test/unit/logging/LogUtil.test.ts @@ -20,13 +20,13 @@ describe('LogUtil', (): void => { }); it('allows setting the global logger factory.', async(): Promise => { - expect(setGlobalLoggerFactory(new VoidLoggerFactory())); + setGlobalLoggerFactory(new VoidLoggerFactory()); expect(LazyLoggerFactory.getInstance().loggerFactory).toBeInstanceOf(VoidLoggerFactory); }); it('allows unsetting the global logger factory.', async(): Promise => { - expect(setGlobalLoggerFactory(new VoidLoggerFactory())); - expect(resetGlobalLoggerFactory()); + setGlobalLoggerFactory(new VoidLoggerFactory()); + resetGlobalLoggerFactory(); expect((): any => LazyLoggerFactory.getInstance().loggerFactory) .toThrow(new Error('No logger factory has been set. Can be caused by logger invocation during initialization.')); }); diff --git a/test/unit/server/ExpressHttpServer.test.ts b/test/unit/server/ExpressHttpServer.test.ts index 3c73cf739f..56c5e74064 100644 --- a/test/unit/server/ExpressHttpServer.test.ts +++ b/test/unit/server/ExpressHttpServer.test.ts @@ -72,7 +72,7 @@ describe('ExpressHttpServer', (): void => { .map((method: string): string => method.trim()); const allowedMethods = [ 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE' ]; expect(corsMethods).toEqual(expect.arrayContaining(allowedMethods)); - expect(corsMethods.length).toBe(allowedMethods.length); + expect(corsMethods).toHaveLength(allowedMethods.length); }); it('specifies CORS origin header if an origin was supplied.', async(): Promise => { diff --git a/test/unit/storage/LockingResourceStore.test.ts b/test/unit/storage/LockingResourceStore.test.ts index f0519754ae..e0a8484476 100644 --- a/test/unit/storage/LockingResourceStore.test.ts +++ b/test/unit/storage/LockingResourceStore.test.ts @@ -123,6 +123,7 @@ describe('A LockingResourceStore', (): void => { it('destroys the resource and releases the lock when the readable errors.', async(): Promise => { // Make the representation error const representation = await store.getRepresentation({ path: 'path' }, {}); + // eslint-disable-next-line jest/valid-expect-in-promise Promise.resolve().then((): any => representation.data.emit('error', new Error('Error on the readable')), null); await registerEventOrder(representation.data, 'error'); @@ -176,7 +177,7 @@ describe('A LockingResourceStore', (): void => { // Wait 1000ms and read jest.advanceTimersByTime(1000); - expect(representation.data.read()).toBe(null); + expect(representation.data.read()).toBeNull(); await registerEventOrder(representation.data, 'close'); // Verify a timeout error was thrown @@ -207,7 +208,7 @@ describe('A LockingResourceStore', (): void => { // Wait 1000ms and watch the stream be destroyed jest.advanceTimersByTime(1000); - expect(representation.data.read()).toBe(null); + expect(representation.data.read()).toBeNull(); await registerEventOrder(representation.data, 'close'); // Verify a timeout error was thrown diff --git a/test/unit/storage/SingleThreadedResourceLocker.test.ts b/test/unit/storage/SingleThreadedResourceLocker.test.ts index b4b51907e3..6cea9aeb2e 100644 --- a/test/unit/storage/SingleThreadedResourceLocker.test.ts +++ b/test/unit/storage/SingleThreadedResourceLocker.test.ts @@ -23,6 +23,7 @@ describe('A SingleThreadedResourceLocker', (): void => { expect(lock).toEqual(expect.objectContaining({ release: expect.any(Function) })); }); + /* eslint-disable jest/valid-expect-in-promise */ it('blocks lock acquisition until they are released.', async(): Promise => { const results: number[] = []; const lock1 = locker.acquire({ path: 'path' }); diff --git a/test/unit/storage/accessors/FileDataAccessor.test.ts b/test/unit/storage/accessors/FileDataAccessor.test.ts index eaf545e313..9bdfdc6072 100644 --- a/test/unit/storage/accessors/FileDataAccessor.test.ts +++ b/test/unit/storage/accessors/FileDataAccessor.test.ts @@ -129,11 +129,11 @@ describe('A FileDataAccessor', (): void => { it('adds stored metadata when requesting metadata.', async(): Promise => { cache.data = { resource: 'data', 'resource.meta': ' .' }; metadata = await accessor.getMetadata({ path: `${base}resource` }); - expect(metadata.quads().some((quad): boolean => quad.subject.value === 'this')); + expect(metadata.quads().some((quad): boolean => quad.subject.value === 'this')).toBe(true); cache.data = { container: { '.meta': ' .' }}; metadata = await accessor.getMetadata({ path: `${base}container/` }); - expect(metadata.quads().some((quad): boolean => quad.subject.value === 'this')); + expect(metadata.quads().some((quad): boolean => quad.subject.value === 'this')).toBe(true); }); it('throws an error if there is a problem with the internal metadata.', async(): Promise => { diff --git a/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts b/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts index 3960870209..8ef80a3111 100644 --- a/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts +++ b/test/unit/storage/patch/SparqlUpdatePatchHandler.test.ts @@ -63,7 +63,7 @@ describe('A SparqlUpdatePatchHandler', (): void => { handler = new SparqlUpdatePatchHandler(source, locker); }); - const basicChecks = async(quads: Quad[]): Promise => { + const basicChecks = async(quads: Quad[]): Promise => { expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenLastCalledWith( { path: 'path' }, { type: [{ value: INTERNAL_QUADS, weight: 1 }]}, @@ -78,6 +78,7 @@ describe('A SparqlUpdatePatchHandler', (): void => { })); expect(setParams[1].metadata.contentType).toEqual(INTERNAL_QUADS); await expect(arrayifyStream(setParams[1].data)).resolves.toBeRdfIsomorphic(quads); + return true; }; it('only accepts SPARQL updates.', async(): Promise => { @@ -95,10 +96,10 @@ describe('A SparqlUpdatePatchHandler', (): void => { ' }', { quads: true }, ) } as SparqlUpdatePatch }); - await basicChecks(startQuads.concat( + expect(await basicChecks(startQuads.concat( [ quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')), quad(namedNode('http://test.com/s2'), namedNode('http://test.com/p2'), namedNode('http://test.com/o2')) ], - )); + ))).toBe(true); }); it('handles DELETE DATA updates.', async(): Promise => { @@ -107,11 +108,11 @@ describe('A SparqlUpdatePatchHandler', (): void => { 'DELETE DATA { }', { quads: true }, ) } as SparqlUpdatePatch }); - await basicChecks( + expect(await basicChecks( [ quad(namedNode('http://test.com/startS2'), namedNode('http://test.com/startP2'), namedNode('http://test.com/startO2')) ], - ); + )).toBe(true); }); it('handles DELETE WHERE updates with no variables.', async(): Promise => { @@ -120,11 +121,11 @@ describe('A SparqlUpdatePatchHandler', (): void => { 'DELETE WHERE { }', { quads: true }, ) } as SparqlUpdatePatch }); - await basicChecks( + expect(await basicChecks( [ quad(namedNode('http://test.com/startS2'), namedNode('http://test.com/startP2'), namedNode('http://test.com/startO2')) ], - ); + )).toBe(true); }); it('handles DELETE/INSERT updates with empty WHERE.', async(): Promise => { @@ -135,14 +136,14 @@ describe('A SparqlUpdatePatchHandler', (): void => { 'WHERE {}', { quads: true }, ) } as SparqlUpdatePatch }); - await basicChecks([ + expect(await basicChecks([ quad(namedNode('http://test.com/startS2'), namedNode('http://test.com/startP2'), namedNode('http://test.com/startO2')), quad(namedNode('http://test.com/s1'), namedNode('http://test.com/p1'), namedNode('http://test.com/o1')), - ]); + ])).toBe(true); }); it('rejects GRAPH inserts.', async(): Promise => { diff --git a/test/util/TestHelpers.ts b/test/util/TestHelpers.ts index 6595d49adf..519015ebc5 100644 --- a/test/util/TestHelpers.ts +++ b/test/util/TestHelpers.ts @@ -14,6 +14,7 @@ import type { HttpRequest } from '../../src/server/HttpRequest'; import { CONTENT_TYPE } from '../../src/util/UriConstants'; import { call } from './Util'; +/* eslint-disable jest/no-standalone-expect */ export class AclTestHelper { public readonly store: ResourceStore; public id: string;