add e2e tests for CTB singularNames + better handle names containing numbers

This commit is contained in:
Pierre Noël 2021-09-14 15:30:07 +02:00
parent 64940edbae
commit 7f0877b73f
9 changed files with 367 additions and 161 deletions

View File

@ -64,11 +64,13 @@ describe('Content type validator', () => {
});
});
describe('Prevents use of names without plural form', () => {
test('Throws when using name without plural form', async () => {
describe('Prevents use of same singularName and pluralName', () => {
test('Throws when using same singularName and pluralName', async () => {
const data = {
contentType: {
name: 'news',
displayName: 'news',
singularName: 'news',
pluralName: 'news',
attributes: {
title: {
type: 'string',
@ -79,7 +81,7 @@ describe('Content type validator', () => {
await validateContentTypeInput(data).catch(err => {
expect(err).toMatchObject({
'contentType.name': [expect.stringMatching('cannot be pluralized')],
contentType: [expect.stringMatching('singularName and pluralName should be different')],
});
});
});
@ -89,7 +91,9 @@ describe('Content type validator', () => {
test('Can use custom keys', async () => {
const input = {
contentType: {
name: 'test',
displayName: 'test',
singularName: 'test',
pluralName: 'tests',
attributes: {
views: {
type: 'integer',
@ -115,7 +119,9 @@ describe('Content type validator', () => {
test('Deletes empty defaults', async () => {
const data = {
contentType: {
name: 'test',
displayName: 'test',
singularName: 'test',
pluralName: 'tests',
attributes: {
slug: {
type: 'string',
@ -161,7 +167,9 @@ describe('Content type validator', () => {
test('Deleted UID target fields are removed from input data', async () => {
const data = {
contentType: {
name: 'test',
displayName: 'test',
singularName: 'test',
pluralName: 'tests',
attributes: {
slug: {
type: 'uid',
@ -181,7 +189,9 @@ describe('Content type validator', () => {
test('Can use custom keys', async () => {
const input = {
contentType: {
name: 'test',
displayName: 'test',
singularName: 'test',
pluralName: 'tests',
attributes: {
views: {
type: 'integer',

View File

@ -46,7 +46,8 @@ const createContentTypeSchema = (data, { isEdition = false } = {}) => {
const kind = _.get(data, 'contentType.kind', typeKinds.COLLECTION_TYPE);
const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS[kind] || [], {
modelType: modelTypes.CONTENT_TYPE,
}).shape({
})
.shape({
displayName: yup
.string()
.min(1)
@ -65,7 +66,12 @@ const createContentTypeSchema = (data, { isEdition = false } = {}) => {
.test(forbiddenContentTypeNameValidator())
.isKebabCase()
.required(),
});
})
.test(
'singularName-not-equal-pluralName',
'${path}: singularName and pluralName should be different',
value => value.singularName !== value.pluralName
);
return yup
.object({

View File

@ -12,15 +12,17 @@ Object {
},
"collectionName": "tests",
"description": "My description",
"displayName": "My name",
"draftAndPublish": false,
"kind": "singleType",
"name": "My name",
"pluginOptions": Object {
"content-manager": Object {
"visible": true,
},
},
"pluralName": "my-names",
"restrictRelationsTo": null,
"singularName": "my-name",
"visible": true,
},
"uid": "test-uid",

View File

@ -12,6 +12,8 @@ describe('Content types service', () => {
collectionName: 'tests',
info: {
displayName: 'My name',
singularName: 'my-name',
pluralName: 'my-names',
description: 'My description',
},
options: {

View File

@ -59,38 +59,3 @@ Object {
},
}
`;
exports[`Content Type Builder - Content types Single Types Get single type returns full schema and information 1`] = `
Object {
"data": Object {
"apiID": "test-single-type",
"schema": Object {
"attributes": Object {
"title": Object {
"pluginOptions": Object {
"i18n": Object {
"localized": true,
},
},
"type": "string",
},
},
"collectionName": "test_single_types",
"description": "",
"displayName": "Test Single Type",
"draftAndPublish": false,
"kind": "singleType",
"pluginOptions": Object {
"i18n": Object {
"localized": true,
},
},
"pluralName": "test-single-types",
"restrictRelationsTo": null,
"singularName": "test-single-type",
"visible": true,
},
"uid": "api::test-single-type.test-single-type",
},
}
`;

View File

@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Content Type Builder - Content types Single Types Get single type returns full schema and information 1`] = `
Object {
"data": Object {
"apiID": "test-single-type",
"schema": Object {
"attributes": Object {
"title": Object {
"pluginOptions": Object {
"i18n": Object {
"localized": true,
},
},
"type": "string",
},
},
"collectionName": "test_single_types",
"description": "",
"displayName": "Test Single Type",
"draftAndPublish": false,
"kind": "singleType",
"pluginOptions": Object {
"i18n": Object {
"localized": true,
},
},
"pluralName": "test-single-types",
"restrictRelationsTo": null,
"singularName": "test-single-type",
"visible": true,
},
"uid": "api::test-single-type.test-single-type",
},
}
`;

View File

@ -0,0 +1,277 @@
/**
* Integration test for the content-type-builder content types management apis
*/
'use strict';
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../test/helpers/request');
const modelsUtils = require('../../../../test/helpers/models');
let strapi;
let rq;
const restart = async () => {
await strapi.destroy();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
};
describe('Content Type Builder - Content types', () => {
beforeAll(async () => {
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
});
afterEach(async () => {
await restart();
});
afterAll(async () => {
const modelsUIDs = [
'api::test-collection-type.test-collection-type',
'api::ct-with-dp.ct-with-dp',
'api::kebab-case.kebab-case',
'api::my2space.my2space',
'api::my-3-space.my-3-space',
];
await modelsUtils.cleanupModels(modelsUIDs, { strapi });
await modelsUtils.deleteContentTypes(modelsUIDs, { strapi });
await strapi.destroy();
});
describe('Collection Types', () => {
const testCollectionTypeUID = 'api::test-collection-type.test-collection-type';
const ctWithDpUID = 'api::ct-with-dp.ct-with-dp';
test('Successful creation of a collection type', async () => {
const res = await rq({
method: 'POST',
url: '/content-type-builder/content-types',
body: {
contentType: {
displayName: 'Test Collection Type',
singularName: 'test-collection-type',
pluralName: 'test-collection-types',
pluginOptions: {
i18n: {
localized: true,
},
},
attributes: {
title: {
type: 'string',
pluginOptions: {
i18n: {
localized: true,
},
},
},
},
},
},
});
expect(res.statusCode).toBe(201);
expect(res.body).toEqual({
data: {
uid: testCollectionTypeUID,
},
});
});
test('Get collection type returns full schema and information', async () => {
const res = await rq({
method: 'GET',
url: `/content-type-builder/content-types/${testCollectionTypeUID}`,
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchSnapshot();
});
test('Successfull creation of a collection type with draftAndPublish enabled', async () => {
const res = await rq({
method: 'POST',
url: '/content-type-builder/content-types',
body: {
contentType: {
displayName: 'CT with DP',
singularName: 'ct-with-dp',
pluralName: 'ct-with-dps',
draftAndPublish: true,
attributes: {
title: {
type: 'string',
},
},
},
},
});
expect(res.statusCode).toBe(201);
expect(res.body).toEqual({
data: {
uid: ctWithDpUID,
},
});
});
test('Get collection type returns full schema and informations with draftAndPublish', async () => {
const res = await rq({
method: 'GET',
url: `/content-type-builder/content-types/${ctWithDpUID}`,
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchSnapshot();
});
test('Cannot use same string for singularName and pluralName', async () => {
const res = await rq({
method: 'POST',
url: '/content-type-builder/content-types',
body: {
contentType: {
displayName: 'same string',
singularName: 'same-string',
pluralName: 'same-string',
draftAndPublish: true,
attributes: {
title: {
type: 'string',
},
},
},
},
});
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({
error: {
contentType: ['contentType: singularName and pluralName should be different'],
},
});
});
test('displayNamen singularName and pluralName are required', async () => {
const res = await rq({
method: 'POST',
url: '/content-type-builder/content-types',
body: {
contentType: {
draftAndPublish: true,
attributes: {
title: {
type: 'string',
},
},
},
},
});
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({
error: {
contentType: ['contentType: singularName and pluralName should be different'],
'contentType.displayName': ['contentType.displayName is a required field'],
'contentType.singularName': ['contentType.singularName is a required field'],
'contentType.pluralName': ['contentType.pluralName is a required field'],
},
});
});
test('Can edit displayName but singularName and pluralName are ignored', async () => {
const uid = 'api::ct-with-dp.ct-with-dp';
let res = await rq({
method: 'PUT',
url: `/content-type-builder/content-types/${uid}`,
body: {
contentType: {
displayName: 'new displayName',
singularName: 'ct-with-dp-new',
pluralName: 'ct-with-dps-new',
draftAndPublish: true,
attributes: {
title: {
type: 'string',
},
},
},
},
});
expect(res.statusCode).toBe(201);
await restart();
res = await rq({
method: 'GET',
url: `/content-type-builder/content-types/${uid}`,
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchObject({
data: {
uid,
schema: {
displayName: 'new displayName',
singularName: 'ct-with-dp', // no change
pluralName: 'ct-with-dps',
draftAndPublish: true,
attributes: {
title: {
type: 'string',
},
},
},
},
});
});
test.each([
['kebab-case', 'kebab-cases', true],
['Kebab-case', 'Kebab-cases', false],
['kebab case', 'kebab cases', false],
['kebabCase', 'kebabCases', false],
['kebab@case', 'kebab@cases', false],
['my2space', 'my2spaces', true],
['2myspace', '2myspaces', false],
['my-3-space', 'my-3-spaces', true],
])('Are "%s" and "%s" valid: %s', async (singularName, pluralName, isExpectedValid) => {
const res = await rq({
method: 'POST',
url: '/content-type-builder/content-types',
body: {
contentType: {
displayName: 'same string',
singularName,
pluralName,
draftAndPublish: true,
attributes: {
title: {
type: 'string',
},
},
},
},
});
if (isExpectedValid) {
expect(res.statusCode).toBe(201);
} else {
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({
error: {
'contentType.pluralName': [
'contentType.pluralName is not in kebab case (an-example-of-kebab-case)',
],
'contentType.singularName': [
'contentType.singularName is not in kebab case (an-example-of-kebab-case)',
],
},
});
}
});
});
});

View File

@ -28,10 +28,8 @@ describe('Content Type Builder - Content types', () => {
afterAll(async () => {
const modelsUIDs = [
'api::test-collection-type.test-collection-type',
'api::test-collection.test-collection',
'api::test-single-type.test-single-type',
'api::ct-with-dp.ct-with-dp',
];
await modelsUtils.cleanupModels(modelsUIDs, { strapi });
@ -40,94 +38,6 @@ describe('Content Type Builder - Content types', () => {
await strapi.destroy();
});
describe('Collection Types', () => {
const testCollectionTypeUID = 'api::test-collection-type.test-collection-type';
const ctWithDpUID = 'api::ct-with-dp.ct-with-dp';
test('Successful creation of a collection type', async () => {
const res = await rq({
method: 'POST',
url: '/content-type-builder/content-types',
body: {
contentType: {
displayName: 'Test Collection Type',
singularName: 'test-collection-type',
pluralName: 'test-collection-types',
pluginOptions: {
i18n: {
localized: true,
},
},
attributes: {
title: {
type: 'string',
pluginOptions: {
i18n: {
localized: true,
},
},
},
},
},
},
});
expect(res.statusCode).toBe(201);
expect(res.body).toEqual({
data: {
uid: testCollectionTypeUID,
},
});
});
test('Get collection type returns full schema and information', async () => {
const res = await rq({
method: 'GET',
url: `/content-type-builder/content-types/${testCollectionTypeUID}`,
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchSnapshot();
});
test('Successfull creation of a collection type with draftAndPublish enabled', async () => {
const res = await rq({
method: 'POST',
url: '/content-type-builder/content-types',
body: {
contentType: {
displayName: 'CT with DP',
singularName: 'ct-with-dp',
pluralName: 'ct-with-dps',
draftAndPublish: true,
attributes: {
title: {
type: 'string',
},
},
},
},
});
expect(res.statusCode).toBe(201);
expect(res.body).toEqual({
data: {
uid: ctWithDpUID,
},
});
});
test('Get collection type returns full schema and informations with draftAndPublish', async () => {
const res = await rq({
method: 'GET',
url: `/content-type-builder/content-types/${ctWithDpUID}`,
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchSnapshot();
});
});
describe('Single Types', () => {
const singleTypeUID = 'api::test-single-type.test-single-type';
@ -259,12 +169,8 @@ describe('Content Type Builder - Content types', () => {
expect(updateRes.statusCode).toBe(400);
expect(updateRes.body.error).toMatch('multiple entries in DB');
});
});
describe('Private relation field', () => {
const singleTypeUID = 'api::test-single-type.test-single-type';
test('should add a relation field', async () => {
test('Should add a relation field', async () => {
const res = await rq({
method: 'PUT',
url: `/content-type-builder/content-types/${singleTypeUID}`,
@ -294,7 +200,7 @@ describe('Content Type Builder - Content types', () => {
});
});
test('should contain a private relation field', async () => {
test('Should contain a private relation field', async () => {
const res = await rq({
method: 'GET',
url: `/content-type-builder/content-types/${singleTypeUID}`,

View File

@ -4,8 +4,10 @@ const { join, extname, basename } = require('path');
const { existsSync } = require('fs-extra');
const _ = require('lodash');
const fse = require('fs-extra');
const { isKebabCase } = require('@strapi/utils');
const normalizeName = _.kebabCase;
// to handle names with numbers in it we first check is it is already in kebabCase
const normalizeName = name => (isKebabCase(name) ? name : _.kebabCase(name));
const DEFAULT_CONTENT_TYPE = {
schema: {},