Enforced more Mocha lint rules (#19720)

ref https://github.com/TryGhost/Ghost/issues/11038

1. Enforced lint rule
**[ghost/mocha/no-identical-title](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/no-identical-title.md)**
- Fixed relevant tests
2. Enforced lint rule
**[ghost/mocha/max-top-level-suites](https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/max-top-level-suites.md)**
- No required fixes, as tests are compliant already

#### Additional details
Specifically for `ghost/mocha/no-identical-title` most fixes were simple
test description updates. Added comments to aid the PR review for the
ones that had relevant changes, and might require more attention. They
are as follows:
*
[e2e-api/admin/invites.test.js](https://github.com/TryGhost/Ghost/pull/19720#discussion_r1496397548):
Removed duplicated test (exact same code on both);
*
[e2e-api/admin/members.test.js](https://github.com/TryGhost/Ghost/pull/19720#discussion_r1496399107):
From the[ PR this was
introduced](73466c1c40 (diff-4dbc7e96e356428561085147e00e9acb5c71b58d4c1bd3d9fc9ac30e77c45be0L236-L237))
seems like author based his test on an existing one but possibly forgot
to rename it;
*
[unit/api/canary/utils/serializers/input/pages.test.js](https://github.com/TryGhost/Ghost/pull/19720#discussion_r1496400143):
The [page filter](https://github.com/TryGhost/Ghost/pull/14829/files)
was removed, so changed the description accordingly;
*
[unit/api/canary/utils/serializers/input/posts.test.js](https://github.com/TryGhost/Ghost/pull/19720#discussion_r1496400329):
The [page filter](https://github.com/TryGhost/Ghost/pull/14829/files)
was removed, so changed the description accordingly;
*
[unit/frontend/services/rendering/templates.test.js](https://github.com/TryGhost/Ghost/pull/19720#discussion_r1496402430):
Removed duplicated test
*
[unit/server/models/post.test.js](https://github.com/TryGhost/Ghost/pull/19720#discussion_r1496403529):
the change in [this
PR](https://github.com/TryGhost/Ghost/pull/14586/files#diff-c351cb589adefbb886570cfadb33b33eb8fdc12bde1024d1188cd18c165fc5e8L1010)
made three tests here mostly the same. Deduplicated them and kept only
one.
This commit is contained in:
Nicholas Mizoguchi 2024-04-16 09:37:06 +02:00 committed by GitHub
parent 8e8250a332
commit d6b7ebb517
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 47 additions and 152 deletions

View File

@ -31,8 +31,6 @@ module.exports = {
'ghost/mocha/no-skipped-tests': 'error',
// TODO: remove these custom rules and fix problems in test files
'ghost/mocha/max-top-level-suites': 'off',
'ghost/mocha/no-identical-title': 'off',
'ghost/mocha/no-setup-in-describe': 'off',
'ghost/mocha/no-sibling-hooks': 'off'
}

View File

@ -224,32 +224,5 @@ describe('Invites API', function () {
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
});
it('Can add a new invite by API Key with the Contributor Role', async function () {
const roleId = testUtils.getExistingData().roles.find(role => role.name === 'Contributor').id;
const res = await request
.post(localUtils.API.getApiQuery('invites/'))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/admin/')}`)
.send({
invites: [{email: 'admin-api-key-test@example.com', role_id: roleId}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.invites);
jsonResponse.invites.should.have.length(1);
localUtils.API.checkResponse(jsonResponse.invites[0], 'invite');
jsonResponse.invites[0].role_id.should.eql(roleId);
mailService.GhostMailer.prototype.send.called.should.be.true();
should.exist(res.headers.location);
res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('invites/')}${res.body.invites[0].id}/`);
});
});
});

View File

@ -554,7 +554,7 @@ describe('Members API', function () {
});
});
it('Can filter by signup attribution', async function () {
it('Can filter by conversion attribution', async function () {
await agent
.get('/members/?filter=conversion:' + fixtureManager.get('posts', 0).id)
.expectStatus(200)

View File

@ -330,7 +330,7 @@ test.describe('Publishing', () => {
test.describe('Schedule post', () => {
// Post should be published to web and sent as a newsletter at the scheduled time
test('Publish and Email', async ({sharedPage}) => {
test('Scheduled Publish and Email', async ({sharedPage}) => {
const postData = {
// This title should be unique
title: 'Scheduled post publish+email test',
@ -364,7 +364,7 @@ test.describe('Publishing', () => {
});
// Post should be published to web only at the scheduled time
test('Publish only', async ({sharedPage}) => {
test('Scheduled Publish only', async ({sharedPage}) => {
const postData = {
title: 'Scheduled post test',
body: 'This is my scheduled post body.'
@ -394,7 +394,7 @@ test.describe('Publishing', () => {
});
// Post should be published to web only at the scheduled time
test('Email only', async ({sharedPage}) => {
test('Scheduled Email only', async ({sharedPage}) => {
const postData = {
title: 'Scheduled email only test',
body: 'This is my scheduled post body.'

View File

@ -368,7 +368,7 @@ describe('Mentions Service', function () {
assert.equal(endpointMock.isDone(), true);
});
it('Unpublished post (post.unpublished)', async function () {
it('Unpublished page (page.unpublished)', async function () {
const publishedPage = {status: 'published', ...mentionsPost};
const res = await agent
.post('pages/')

View File

@ -272,7 +272,7 @@ describe('Batch sending tests', function () {
]);
});
it('Splits recipients in free and paid batch when including paid member only content', async function () {
it('Splits recipients in free and paid batch when including different paid and free member content', async function () {
await testEmailBatches({
mobiledoc: mobileDocWithPaidAndFreeMemberOnly // = different content for paid and free members
}, null, [

View File

@ -327,7 +327,7 @@ describe('Frontend behavior tests', function () {
});
describe('assets', function () {
it('blog is https, request is http', function () {
it('blog is https, request is http (png)', function () {
const req = {
secure: false,
method: 'GET',
@ -342,7 +342,7 @@ describe('Frontend behavior tests', function () {
});
});
it('blog is https, request is http', function () {
it('blog is https, request is http (css)', function () {
const req = {
secure: false,
method: 'GET',
@ -562,7 +562,7 @@ describe('Frontend behavior tests', function () {
sinon.restore();
});
it('serve post', function () {
it('serve 404 when there is post with given slug', function () {
const req = {
method: 'GET',
@ -744,7 +744,7 @@ describe('Frontend behavior tests', function () {
});
});
it('post without tag', function () {
it('post without tag on something collection', function () {
const req = {
method: 'GET',
url: '/something/html-ipsum/',

View File

@ -16,7 +16,7 @@ describe('Unit: endpoints/utils/serializers/input/pages', function () {
frame.options.filter.should.eql('type:page');
});
it('combine filters', function () {
it('combine status+tag filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
@ -30,7 +30,7 @@ describe('Unit: endpoints/utils/serializers/input/pages', function () {
frame.options.filter.should.eql('(status:published+tag:eins)+type:page');
});
it('combine filters', function () {
it('only tag filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
@ -77,7 +77,7 @@ describe('Unit: endpoints/utils/serializers/input/pages', function () {
frame.options.filter.should.eql('type:page');
});
it('content api default', function () {
it('content api default (with context)', function () {
const apiConfig = {};
const frame = {
apiType: 'content',

View File

@ -37,7 +37,7 @@ describe('Unit: endpoints/utils/serializers/input/posts', function () {
should.equal(frame.options.filter, '(type:post)+status:[draft,published,scheduled,sent]');
});
it('combine filters', function () {
it('combine status+tag filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
@ -57,7 +57,7 @@ describe('Unit: endpoints/utils/serializers/input/posts', function () {
frame.options.filter.should.eql('(status:published+tag:eins)+type:post');
});
it('combine filters', function () {
it('only tag filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',

View File

@ -193,7 +193,7 @@ describe('{{#foreach}} helper', function () {
resultData[_.size(context) - 1].data.should.eql(options.fn.lastCall.args[1].data);
});
it('should handle rowStart and rowEnd for multiple columns (array)', function () {
it('should handle rowStart and rowEnd for multiple columns (object)', function () {
const expected = [
{first: true, last: false, even: false, odd: true, rowStart: true, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: true},

View File

@ -202,7 +202,7 @@ describe('{{#has}} helper', function () {
inverse.called.should.be.false();
});
it('should handle tags and authors like an OR query (pass)', function () {
it('should handle tags and authors like an OR query when match both author and tag (pass)', function () {
thisCtx = {authors: [{name: 'sam'}], tags: [{name: 'much'}, {name: 'bar'}, {name: 'baz'}]};
callHasHelper(thisCtx, {author: 'joe, sam, pat', tag: 'much, such, wow'});
@ -238,7 +238,7 @@ describe('{{#has}} helper', function () {
inverse.called.should.be.false();
});
it('count:>1', function () {
it('count:>1 (fail)', function () {
thisCtx = {authors: [{name: 'fred'}]};
callHasHelper(thisCtx, {author: 'count:>1'});
@ -247,7 +247,7 @@ describe('{{#has}} helper', function () {
inverse.called.should.be.true();
});
it('count:>1', function () {
it('count:>1 (pass)', function () {
thisCtx = {authors: [{name: 'fred'}, {name: 'sam'}]};
callHasHelper(thisCtx, {author: 'count:>1'});

View File

@ -281,7 +281,7 @@ describe('templates', function () {
hasTemplateStub.withArgs('tag-design').returns(true);
});
it('will return correct view for a tag', function () {
it('will return correct view for a tag when template exists', function () {
const view = _private.getTemplateForEntries({name: 'tag', slugTemplate: true}, {slugParam: 'design'});
should.exist(view);
view.should.eql('tag-design');
@ -454,28 +454,6 @@ describe('templates', function () {
stubs.pickTemplate.calledWith('test', 'path/to/local/test.hbs').should.be.true();
});
it('calls pickTemplate for custom routes', function () {
res.routerOptions = {
type: 'custom',
templates: 'test',
defaultTemplate: 'path/to/local/test.hbs'
};
// Call setTemplate
templates.setTemplate(req, res, data);
// should be testFromPickTemplate
res._template.should.eql('testFromPickTemplate');
// Only pickTemplate got called
stubs.pickTemplate.called.should.be.true();
stubs.getTemplateForEntry.called.should.be.false();
stubs.getTemplateForEntries.called.should.be.false();
stubs.getTemplateForError.called.should.be.false();
stubs.pickTemplate.calledWith('test', 'path/to/local/test.hbs').should.be.true();
});
it('calls getTemplateForEntry for entry routes', function () {
res.routerOptions = {
type: 'entry'

View File

@ -280,19 +280,19 @@ describe('UNIT - services/routing/ParentRouter', function () {
});
describe('fn: isRedirectEnabled', function () {
it('no data key defined', function () {
it('data is undefined', function () {
const parentRouter = new ParentRouter();
parentRouter.data = undefined;
parentRouter.isRedirectEnabled('tags', 'bacon').should.be.false();
});
it('no data key defined', function () {
it('data keys are undefined', function () {
const parentRouter = new ParentRouter();
parentRouter.data = {query: {}, router: {}};
should.not.exist(parentRouter.isRedirectEnabled('tags', 'bacon'));
});
it('no redirect', function () {
it('no redirect when unspecified slug', function () {
const parentRouter = new ParentRouter();
parentRouter.data = {
@ -305,7 +305,7 @@ describe('UNIT - services/routing/ParentRouter', function () {
should.not.exist(parentRouter.isRedirectEnabled('tags', 'bacon'));
});
it('no redirect', function () {
it('no redirect when wrong slug', function () {
const parentRouter = new ParentRouter();
parentRouter.data = {
@ -318,7 +318,7 @@ describe('UNIT - services/routing/ParentRouter', function () {
should.not.exist(parentRouter.isRedirectEnabled('tags', 'bacon'));
});
it('no redirect', function () {
it('no redirect when tag redirect=false', function () {
const parentRouter = new ParentRouter();
parentRouter.data = {
@ -331,7 +331,7 @@ describe('UNIT - services/routing/ParentRouter', function () {
should.not.exist(parentRouter.isRedirectEnabled('tags', 'bacon'));
});
it('redirect', function () {
it('redirect (tags)', function () {
const parentRouter = new ParentRouter();
parentRouter.data = {
@ -344,7 +344,7 @@ describe('UNIT - services/routing/ParentRouter', function () {
should.exist(parentRouter.isRedirectEnabled('tags', 'bacon'));
});
it('redirect', function () {
it('redirect (pages)', function () {
const parentRouter = new ParentRouter();
parentRouter.data = {

View File

@ -90,7 +90,7 @@ describe('UNIT - services/routing/StaticRoutesRouter', function () {
should.not.exist(res.locals.slug);
});
it('fn: _prepareStaticRouteContext', function () {
it('fn: _prepareStaticRouteContext (mainRoute=root)', function () {
const staticRoutesRouter = new StaticRoutesRouter('/', {templates: []}, routerCreatedSpy);
staticRoutesRouter._prepareStaticRouteContext(req, res, next);

View File

@ -29,7 +29,7 @@ describe('handleImageSizes middleware', function () {
});
});
it('calls next immediately if the url does not match /size/something/', function (done) {
it('calls next immediately if the url does not match /size/whatever/', function (done) {
const fakeReq = {
url: '/url/whatever/'
};
@ -68,7 +68,7 @@ describe('handleImageSizes middleware', function () {
});
});
it('calls next immediately if the url does not match /size/something/', function (done) {
it('calls next immediately if the url does not match /size//', function (done) {
const fakeReq = {
url: '/size//'
};

View File

@ -30,7 +30,7 @@ describe('Scheduling: utils', function () {
}).catch(done);
});
it('create good adapter', function (done) {
it('create good adapter from custom file', function (done) {
scope.adapter = schedulingPath + 'another-scheduler.js';
configUtils.set({

View File

@ -153,7 +153,7 @@ describe('Local Images Storage', function () {
});
});
it('success', function (done) {
it('success (leading and trailing slashes)', function (done) {
localFileStore.read({path: '/ghost-logo.png/'})
.then(function (bytes) {
bytes.length.should.eql(8638);

View File

@ -132,7 +132,7 @@ describe('storage utils', function () {
result.should.be.equal(true);
});
it('should return true when relative URL and local file', function () {
it('should return true when relative URL and local file (blog subdir)', function () {
const url = '/blog/content/images/2017/07/ghost-logo.png';
let result;

View File

@ -883,60 +883,6 @@ describe('Unit: models/post: uses database (@TODO: fix me)', function () {
});
});
it('rejects if changing authors', function (done) {
const mockPostObj = {
related: sinon.stub()
};
const context = {user: 1};
const unsafeAttrs = {authors: [{id: 2}]};
mockPostObj.related.withArgs('authors').returns({models: [{id: 1}]});
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
testUtils.permissions.author,
false,
true,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(errors.NoPermissionError);
should(mockPostObj.related.calledTwice).be.true();
done();
});
});
it('rejects if changing authors', function (done) {
const mockPostObj = {
related: sinon.stub()
};
const context = {user: 1};
const unsafeAttrs = {authors: [{id: 2}]};
mockPostObj.related.withArgs('authors').returns({models: [{id: 1}]});
models.Post.permissible(
mockPostObj,
'edit',
context,
unsafeAttrs,
testUtils.permissions.author,
false,
true,
true
).then(() => {
done(new Error('Permissible function should have rejected.'));
}).catch((error) => {
error.should.be.an.instanceof(errors.NoPermissionError);
mockPostObj.related.callCount.should.eql(2);
done();
});
});
it('resolves if none of the above cases are true', function () {
const mockPostObj = {
related: sinon.stub()

View File

@ -76,7 +76,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without leading and trailing slashes (routes)', function () {
try {
validate({
routes: {
@ -91,7 +91,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without trailing slashes (routes)', function () {
try {
validate({
routes: {
@ -106,7 +106,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without leading slashes (routes)', function () {
try {
validate({
routes: {
@ -121,7 +121,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without leading slashes (collections)', function () {
try {
validate({
collections: {
@ -138,7 +138,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without leading and trailing slashes (collections)', function () {
try {
validate({
collections: {
@ -155,7 +155,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without trailing slashes (collections)', function () {
try {
validate({
collections: {
@ -172,7 +172,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without trailing slashes (permalink)', function () {
try {
validate({
collections: {
@ -189,7 +189,7 @@ describe('UNIT: services/settings/validate', function () {
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
it('throws error without leading and trailing slashes (permalink)', function () {
try {
validate({
collections: {

View File

@ -169,7 +169,7 @@ describe('Unit: services/url/Queue', function () {
});
});
it('start twice', function (done) {
it('start twice with subscriber between starts', function (done) {
let notified = 0;
let called = 0;

View File

@ -86,7 +86,7 @@ describe('Unit: services/url/UrlService', function () {
});
describe('fn: getResource', function () {
it('no resource for url found', function () {
it('no resource for url found (throws GhostError)', function () {
urlService.finished = false;
urlService.urls.getByUrl.withArgs('/blog-post/').returns([]);
@ -130,7 +130,7 @@ describe('Unit: services/url/UrlService', function () {
urlService.getResource('/blog-post/').should.eql(object2.resource);
});
it('two resources for url found', function () {
it('two resources for url found (reverse registration order)', function () {
const object1 = {generatorId: 0, resource: {a: 1}};
const object2 = {generatorId: 1, resource: {a: 2}};
@ -150,7 +150,7 @@ describe('Unit: services/url/UrlService', function () {
});
describe('fn: getPermalinkByUrl', function () {
it('found', function () {
it('found (primary_tag)', function () {
urlService.urlGenerators = [
{
uid: 0,
@ -168,7 +168,7 @@ describe('Unit: services/url/UrlService', function () {
urlService.getPermalinkByUrl('/blog-post/').should.eql('/:primary_tag/');
});
it('found', function () {
it('found (slug)', function () {
urlService.urlGenerators = [
{
uid: 0,

View File

@ -113,7 +113,7 @@ describe('cors', function () {
done();
});
it('should be enabled if the origin matches config.url', function (done) {
it('should be enabled if the origin matches config.admin.url', function (done) {
const origin = 'http://admin:2222';
configUtils.set({