mirror of
https://github.com/strapi/strapi.git
synced 2025-07-30 20:39:05 +00:00

* fix: date comparison * feat(core): document service publish multiple locales (#20046) * feat(core): document service (un)publish multiple locales * fix(core): cleanup locale param types * feat(content-manager): wip - multiple locale support * Count draft relations across locales (#20116) * feat(content-manager): publish multiple locales from CM route * feat(core): multiple locales in document service discard draft * feat(content-manager): use bulkpublish for locale publish * feat(content-manager): use query from bulkpublish locales * feat(content-manager): publishMany * feat(content-manager): api test for bulk locale + document publish * chore(content-manager): fix for build * fix(core): avoid * in locale data * chore(content-manager): pr amends and validation improvement * feat(content-manager): use transaction in document manager publish many * feat(core): throw when a non string locale is provided and not supported * fix(core): doc service find many * chore: clean up * feat(content-manager): Select fields that require validation in availablelocales (#20156) * feat(content-manager): wip - select more fields in availablelocales * feat(content-manager): wip available locales using entity traversal * feat(content-manager): wip test available locales returns fields with validation * feat(content-manager): test available locales returns fields with validation * fix(content-manager): ensure sensitive info not exposed in available statuses * fix(content-manager): sanitize document metadata available status * fix(content-manager): sanitize document metadata available status at controller level * fix(content-manager): populate only for validatable fields * chore: clean up * chore: clean up * chore: clean up * fix(content-manager): build issues * fix(content-manager): allow null locales * fix(content-manager): history service (#20185) * fix(content-manager): history service * chore(content-manager): clean up * fix: pr feedback * chore: update actions deps * chore: update utility deps * chore: upgrade sentry * chore: upgrade graphql-tools * fix: http-errors ugprade * chore: add fs-extra types where needed * docs(typescript): type system cheat sheet * chore!: remove deprecated verbose option from ts:generate-types * chore: clean up fix(content-manager) correctly count bulk publish results * fix(content-manager): pr feedback and test improvements * feat(i18n): bulk locale publish modal in CM edit view (#20069) * feat(i18n): wip bulk locale publish modal * fix(i18n): wip - fe bulk locale publish * feat(content-manager): multi locale publish, integrate with backend and add basic e2e test * feat(i18n): wip - display validation errors in bulk locale modal * chore: clean up * chore(i18n): design system changes * feat(i18n): display correct status after publish and clean up error messaging * feat(i18n): access onclose from modal body * fix(i18n): selected locale change * fix(i18n): locale table state * fix(i18n): edit view e2e test * chore(content-manager): validation tweak wip * feat(i18n): cover validation cases in i18n e2e tests * chore: clean up * fix(i18n): edit view more document actions disabled state * chore: feedback * fix(i18n): send all params to publish many * fix(i18n): place bulk locale publish 3rd in array * fix(content-manager): validation error extraction * fix(content-manager): pr feedback * fix: build * chore(content-manager): simplify exports * chore(i18n): revert package --------- Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com> Co-authored-by: Jean-Sébastien Herbaux <jean-sebastien.herbaux@epitech.eu> Co-authored-by: Ben Irvin <ben.irvin@strapi.io> * fix(i18n): disable publish button after bulk locale publish * fix(content-manager): ce e2e fix * chore: typography * fix(i18n): e2e edit view test * fix: wip date comparison * fix: wip date comparison * fix(content-manager): simplify date comparison * fix: clean up metadata api test * chore: update api tests * fix: draft versions must be ahead of published to be considered modified * fix: published modified calculation * fix: clean up * fix: simplify draft and publish comparison * chore: clean up * fix: flaky fe tests * fix: pr feedback * fix(i18n): error message extraction in bulk locale modal * chore: remove old publish action * chore: pr feedback * chore: refactor error types & validation messages * chore: use anchor link for locale changes * chore: fix releases * fix: clean up * chore: snapshot --------- Co-authored-by: Marc-Roig <marc12info@gmail.com> Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com> Co-authored-by: Jean-Sébastien Herbaux <jean-sebastien.herbaux@epitech.eu> Co-authored-by: Ben Irvin <ben.irvin@strapi.io> Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com>
388 lines
11 KiB
JavaScript
388 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
// Test a simple default API with no relations
|
|
|
|
const { createTestBuilder } = require('api-tests/builder');
|
|
const { createStrapiInstance } = require('api-tests/strapi');
|
|
const { createAuthRequest } = require('api-tests/request');
|
|
const { async } = require('@strapi/utils');
|
|
|
|
const builder = createTestBuilder();
|
|
let strapi;
|
|
let rq;
|
|
const data = {
|
|
productsWithCompoAndDP: [],
|
|
};
|
|
|
|
const compo = {
|
|
displayName: 'compo',
|
|
attributes: {
|
|
name: {
|
|
type: 'string',
|
|
required: true,
|
|
},
|
|
description: {
|
|
type: 'text',
|
|
minLength: 4,
|
|
maxLength: 30,
|
|
},
|
|
},
|
|
};
|
|
|
|
const productWithCompoAndDP = {
|
|
attributes: {
|
|
name: {
|
|
type: 'string',
|
|
},
|
|
description: {
|
|
type: 'text',
|
|
},
|
|
compo: {
|
|
type: 'component',
|
|
component: 'default.compo',
|
|
required: true,
|
|
},
|
|
},
|
|
draftAndPublish: true,
|
|
displayName: 'product with compo and DP',
|
|
singularName: 'product-with-compo-and-dp',
|
|
pluralName: 'product-with-compo-and-dps',
|
|
description: '',
|
|
collectionName: '',
|
|
pluginOptions: {
|
|
i18n: {
|
|
localized: true,
|
|
},
|
|
},
|
|
};
|
|
|
|
const extraLocales = ['fr', 'it', 'es'];
|
|
|
|
describe('CM API - Basic + compo', () => {
|
|
beforeAll(async () => {
|
|
await builder.addComponent(compo).addContentType(productWithCompoAndDP).build();
|
|
|
|
strapi = await createStrapiInstance();
|
|
rq = await createAuthRequest({ strapi });
|
|
|
|
// Create new locales
|
|
for (const extraLocale of extraLocales) {
|
|
await rq({
|
|
method: 'POST',
|
|
url: '/i18n/locales',
|
|
body: {
|
|
code: extraLocale,
|
|
name: `Locale name: (${extraLocale})`,
|
|
isDefault: false,
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await strapi.destroy();
|
|
await builder.cleanup();
|
|
});
|
|
|
|
test('Create product with compo', async () => {
|
|
const product = {
|
|
name: 'Product 1',
|
|
description: 'Product description',
|
|
compo: {
|
|
name: 'compo name',
|
|
description: 'short',
|
|
},
|
|
};
|
|
const res = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(201);
|
|
expect(res.body.data).toMatchObject(product);
|
|
expect(res.body.data.publishedAt).toBeNull();
|
|
data.productsWithCompoAndDP.push(res.body.data);
|
|
});
|
|
|
|
test('Read product with compo', async () => {
|
|
const res = await rq({
|
|
method: 'GET',
|
|
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${data.productsWithCompoAndDP[0].documentId}`,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.body.data).toMatchObject(data.productsWithCompoAndDP[0]);
|
|
expect(res.body.data.publishedAt).toBeNull();
|
|
});
|
|
|
|
test('Update product with compo', async () => {
|
|
const product = {
|
|
name: 'Product 1 updated',
|
|
description: 'Updated Product description',
|
|
compo: {
|
|
name: 'compo name updated',
|
|
description: 'update',
|
|
},
|
|
};
|
|
const res = await rq({
|
|
method: 'PUT',
|
|
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${data.productsWithCompoAndDP[0].documentId}`,
|
|
body: product,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.body.data).toMatchObject(product);
|
|
expect(res.body.data.documentId).toEqual(data.productsWithCompoAndDP[0].documentId);
|
|
expect(res.body.data.publishedAt).toBeNull();
|
|
data.productsWithCompoAndDP[0] = res.body.data;
|
|
});
|
|
|
|
test('Delete product with compo', async () => {
|
|
const res = await rq({
|
|
method: 'DELETE',
|
|
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${data.productsWithCompoAndDP[0].documentId}`,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
data.productsWithCompoAndDP.shift();
|
|
});
|
|
|
|
describe('validation', () => {
|
|
test('Can create product with compo - compo required', async () => {
|
|
const product = {
|
|
name: 'Product 1',
|
|
description: 'Product description',
|
|
compo: null,
|
|
};
|
|
const res = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(201);
|
|
expect(res.body.data).toMatchObject(product);
|
|
data.productsWithCompoAndDP.push(res.body.data);
|
|
});
|
|
|
|
test('Can create product with compo - minLength', async () => {
|
|
const product = {
|
|
name: 'Product 1',
|
|
description: 'Product description',
|
|
compo: {
|
|
name: 'compo name',
|
|
description: '',
|
|
},
|
|
};
|
|
const res = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(201);
|
|
expect(res.body.data).toMatchObject(product);
|
|
data.productsWithCompoAndDP.push(res.body.data);
|
|
});
|
|
|
|
test('Cannot create product with compo - maxLength', async () => {
|
|
const product = {
|
|
name: 'Product 1',
|
|
description: 'Product description',
|
|
compo: {
|
|
name: 'compo name',
|
|
description: 'A very long description that exceed the min length.',
|
|
},
|
|
};
|
|
const res = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(400);
|
|
expect(res.body).toMatchObject({
|
|
data: null,
|
|
error: {
|
|
status: 400,
|
|
name: 'ValidationError',
|
|
message: 'compo.description must be at most 30 characters',
|
|
details: {
|
|
errors: [
|
|
{
|
|
path: ['compo', 'description'],
|
|
message: 'compo.description must be at most 30 characters',
|
|
name: 'ValidationError',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test('Can create product with compo - required', async () => {
|
|
const product = {
|
|
name: 'Product 1',
|
|
description: 'Product description',
|
|
compo: {
|
|
description: 'short',
|
|
},
|
|
};
|
|
const res = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product,
|
|
});
|
|
|
|
expect(res.statusCode).toBe(201);
|
|
expect(res.body.data).toMatchObject(product);
|
|
data.productsWithCompoAndDP.push(res.body.data);
|
|
});
|
|
});
|
|
|
|
describe('Publication', () => {
|
|
test('Can publish product with compo - required', async () => {
|
|
const product = {
|
|
name: 'Product 1',
|
|
description: 'Product description',
|
|
compo: {
|
|
name: 'compo name',
|
|
description: 'short',
|
|
},
|
|
};
|
|
const res = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product,
|
|
});
|
|
|
|
const publishRes = await rq({
|
|
method: 'POST',
|
|
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${res.body.data.documentId}/actions/publish`,
|
|
body: product,
|
|
});
|
|
|
|
expect(publishRes.statusCode).toBe(200);
|
|
// TODO: Validate document is published
|
|
});
|
|
|
|
test('Can bulk publish product with compo - required', async () => {
|
|
const product1 = {
|
|
name: 'Product 1',
|
|
description: 'Product description',
|
|
compo: {
|
|
name: 'compo name',
|
|
description: 'short',
|
|
},
|
|
};
|
|
|
|
const product2 = {
|
|
name: 'Product 2',
|
|
description: 'Product description',
|
|
compo: {
|
|
name: 'compo name',
|
|
description: 'short',
|
|
},
|
|
};
|
|
|
|
const {
|
|
body: {
|
|
data: { documentId: documentId1 },
|
|
},
|
|
} = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product1,
|
|
});
|
|
|
|
const {
|
|
body: {
|
|
data: { documentId: documentId2 },
|
|
},
|
|
} = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product2,
|
|
});
|
|
|
|
const publishRes = await rq({
|
|
method: 'POST',
|
|
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/actions/bulkPublish`,
|
|
body: {
|
|
documentIds: [documentId1, documentId2],
|
|
},
|
|
});
|
|
|
|
expect(publishRes.statusCode).toBe(200);
|
|
expect(publishRes.body).toMatchObject({ count: 2 });
|
|
});
|
|
|
|
test('BulkPublish across multiple documents and locales', async () => {
|
|
// Create multiple documents in the default locales
|
|
const numberOfDocuments = 5;
|
|
const defaultDocuments = {};
|
|
for (let i = 0; i < numberOfDocuments; i += 1) {
|
|
const product = {
|
|
name: `Product ${i}`,
|
|
description: `Product description ${i}`,
|
|
compo: {
|
|
name: `compo name ${i}`,
|
|
description: `short ${i}`,
|
|
},
|
|
};
|
|
|
|
const {
|
|
body: {
|
|
data: { documentId },
|
|
},
|
|
} = await rq({
|
|
method: 'POST',
|
|
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
|
|
body: product,
|
|
});
|
|
|
|
defaultDocuments[documentId] = product;
|
|
}
|
|
|
|
// Add extra locales to each document
|
|
await async.map(Object.entries(defaultDocuments), async ([documentId, product]) => {
|
|
await async.map(extraLocales, async (locale) => {
|
|
await rq({
|
|
method: 'PUT',
|
|
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${documentId}`,
|
|
body: {
|
|
name: `Product ${product.name} ${locale}`,
|
|
compo: {
|
|
name: `compo name ${product.compo.name} ${locale}`,
|
|
description: `short ${product.compo.description} ${locale}`,
|
|
},
|
|
},
|
|
qs: {
|
|
locale,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
// Bulk publish all the documents
|
|
const bulkPublishRes = await rq({
|
|
method: 'POST',
|
|
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/actions/bulkPublish`,
|
|
body: {
|
|
documentIds: Object.keys(defaultDocuments),
|
|
},
|
|
qs: {
|
|
locale: ['en', ...extraLocales],
|
|
},
|
|
});
|
|
|
|
expect(bulkPublishRes.statusCode).toBe(200);
|
|
expect(bulkPublishRes.body).toMatchObject({
|
|
count: numberOfDocuments * (extraLocales.length + 1),
|
|
});
|
|
// TODO verify that all the drafts are still there
|
|
});
|
|
});
|
|
});
|