Merge remote-tracking branch 'origin/main' into fix/relations/rel-error-handling

This commit is contained in:
Jamie Howard 2022-11-10 15:21:56 +00:00
commit ae4b342bc4
11 changed files with 57 additions and 317 deletions

View File

@ -9,6 +9,7 @@ const FilterSelect = ({ message, value, onChange, possibleFilters, onClear, cust
return (
<Select
data-testid={`${message}-button`}
aria-label={message}
placeholder={message}
size="M"
@ -20,7 +21,7 @@ const FilterSelect = ({ message, value, onChange, possibleFilters, onClear, cust
>
{Object.entries(possibleFilters).map(([filterName, count]) => {
return (
<Option key={filterName} value={filterName}>
<Option data-testid={`${filterName}-${count}`} key={filterName} value={filterName}>
{computeFilterMessage(filterName, count)}
</Option>
);

View File

@ -51,6 +51,7 @@ const NpmPackagesFilters = ({
<ButtonToggle
variant="tertiary"
ref={buttonRef}
data-testid="filters-button"
startIcon={<Filter />}
onClick={handleToggle}
size="S"

View File

@ -1378,6 +1378,7 @@ exports[`Marketplace page - layout renders the online layout 1`] = `
<button
aria-disabled="false"
class="c52 c53 c54"
data-testid="filters-button"
type="button"
>
<div

View File

@ -1378,6 +1378,7 @@ exports[`Marketplace page - plugins tab renders and matches the plugin tab snaps
<button
aria-disabled="false"
class="c52 c53 c54"
data-testid="filters-button"
type="button"
>
<div

View File

@ -1373,6 +1373,7 @@ exports[`Marketplace page - providers tab renders and matches the providers tab
<button
aria-disabled="false"
class="c52 c53 c54"
data-testid="filters-button"
type="button"
>
<div

View File

@ -66,7 +66,6 @@ const App = (
const waitForReload = async () => {
await waitFor(() => {
expect(screen.queryByTestId('loader')).toBe(null);
expect(screen.getByRole('heading', { name: /marketplace/i })).toBeInTheDocument();
});
};

View File

@ -54,7 +54,6 @@ const waitForReload = async () => {
await waitFor(
() => {
expect(screen.queryByTestId('loader')).toBe(null);
expect(screen.getByRole('heading', { name: /marketplace/i })).toBeInTheDocument();
},
{ timeout: 5000 }
);
@ -151,21 +150,21 @@ describe('Marketplace page - plugins tab', () => {
});
it('shows plugins filters popover', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' });
const collectionsButton = screen.getByTestId('Collections-button');
const categoriesButton = screen.getByTestId('Categories-button');
expect(collectionsButton).toBeVisible();
expect(categoriesButton).toBeVisible();
});
it('shows the collections filter options', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
@ -177,16 +176,16 @@ describe('Marketplace page - plugins tab', () => {
};
Object.entries(mockedServerCollections).forEach(([collectionName, count]) => {
const option = screen.getByRole('option', { name: `${collectionName} (${count})` });
const option = screen.getByTestId(`${collectionName}-${count}`);
expect(option).toBeVisible();
});
});
it('shows the categories filter options', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' });
const categoriesButton = screen.getByTestId('Categories-button');
userEvent.click(categoriesButton);
@ -197,16 +196,16 @@ describe('Marketplace page - plugins tab', () => {
};
Object.entries(mockedServerCategories).forEach(([categoryName, count]) => {
const option = screen.getByRole('option', { name: `${categoryName} (${count})` });
const option = screen.getByTestId(`${categoryName}-${count}`);
expect(option).toBeVisible();
});
});
it('filters a collection option', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (13)` });
@ -227,10 +226,10 @@ describe('Marketplace page - plugins tab', () => {
});
it('filters a category option', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' });
const categoriesButton = screen.getByTestId('Categories-button');
userEvent.click(categoriesButton);
const option = screen.getByRole('option', { name: `Custom fields (4)` });
@ -252,9 +251,9 @@ describe('Marketplace page - plugins tab', () => {
it('filters a category and a collection option', async () => {
// When a user clicks the filters button
userEvent.click(screen.getByRole('button', { name: /filters/i }));
userEvent.click(screen.getByTestId('filters-button'));
// They should see a select button for collections with no options selected
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
// When they click the select button
userEvent.click(collectionsButton);
// They should see a Made by Strapi option
@ -264,11 +263,11 @@ describe('Marketplace page - plugins tab', () => {
// The page should reload
await waitForReload();
// When they click the filters button again
userEvent.click(screen.getByRole('button', { name: 'Filters' }));
userEvent.click(screen.getByTestId('filters-button'));
// They should see the collections button indicating 1 option selected
userEvent.click(screen.getByRole('button', { name: '1 collection selected Made by Strapi' }));
// They should the categories button with no options selected
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' });
const categoriesButton = screen.getByTestId('Categories-button');
userEvent.click(categoriesButton);
const categoryOption = screen.getByRole('option', { name: `Custom fields (4)` });
userEvent.click(categoryOption);
@ -295,13 +294,13 @@ describe('Marketplace page - plugins tab', () => {
});
it('filters multiple collection options', async () => {
userEvent.click(screen.getByRole('button', { name: /filters/i }));
userEvent.click(screen.getByRole('button', { name: 'No collections selected' }));
userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByTestId('Collections-button'));
userEvent.click(screen.getByRole('option', { name: `Made by Strapi (13)` }));
await waitForReload();
userEvent.click(screen.getByRole('button', { name: /filters/i }));
userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: `1 collection selected Made by Strapi` }));
userEvent.click(screen.getByRole('option', { name: `Verified (29)` }));
@ -318,13 +317,13 @@ describe('Marketplace page - plugins tab', () => {
});
it('filters multiple category options', async () => {
userEvent.click(screen.getByRole('button', { name: /filters/i }));
userEvent.click(screen.getByRole('button', { name: 'No categories selected' }));
userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByTestId('Categories-button'));
userEvent.click(screen.getByRole('option', { name: `Custom fields (4)` }));
await waitForReload();
userEvent.click(screen.getByRole('button', { name: /filters/i }));
userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: `1 category selected Custom fields` }));
userEvent.click(screen.getByRole('option', { name: `Monitoring (1)` }));
@ -341,10 +340,10 @@ describe('Marketplace page - plugins tab', () => {
});
it('removes a filter option tag', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (13)` });
@ -362,10 +361,10 @@ describe('Marketplace page - plugins tab', () => {
});
it('only filters in the plugins tab', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (13)` });

View File

@ -53,7 +53,6 @@ const waitForReload = async () => {
await waitFor(
() => {
expect(screen.queryByTestId('loader')).toBe(null);
expect(screen.getByRole('heading', { name: /marketplace/i })).toBeInTheDocument();
},
{ timeout: 5000 }
);
@ -158,7 +157,7 @@ describe('Marketplace page - providers tab', () => {
});
it('shows providers filters popover', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
// Only show collections filters on providers
const providersTab = screen.getByRole('tab', { name: /providers/i });
@ -168,10 +167,10 @@ describe('Marketplace page - providers tab', () => {
});
it('shows the collections filter options', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
@ -183,16 +182,16 @@ describe('Marketplace page - providers tab', () => {
};
Object.entries(mockedServerCollections).forEach(([collectionName, count]) => {
const option = screen.getByRole('option', { name: `${collectionName} (${count})` });
const option = screen.getByTestId(`${collectionName}-${count}`);
expect(option).toBeVisible();
});
});
it('filters a collection option', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (6)` });
@ -213,13 +212,13 @@ describe('Marketplace page - providers tab', () => {
});
it('filters multiple collection options', async () => {
userEvent.click(screen.getByRole('button', { name: /filters/i }));
userEvent.click(screen.getByRole('button', { name: 'No collections selected' }));
userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByTestId('Collections-button'));
userEvent.click(screen.getByRole('option', { name: `Made by Strapi (6)` }));
await waitForReload();
userEvent.click(screen.getByRole('button', { name: /filters/i }));
userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: `1 collection selected Made by Strapi` }));
userEvent.click(screen.getByRole('option', { name: `Verified (6)` }));
@ -236,10 +235,10 @@ describe('Marketplace page - providers tab', () => {
});
it('removes a filter option tag', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (6)` });
@ -257,10 +256,10 @@ describe('Marketplace page - providers tab', () => {
});
it('only filters in the providers tab', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i });
const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' });
const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (6)` });

View File

@ -1,75 +0,0 @@
'use strict';
// Helpers.
const { createTestBuilder } = require('../../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../../test/helpers/strapi');
const form = require('../../../../../../test/helpers/generators');
const { createAuthRequest } = require('../../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
let rq;
const restart = async () => {
await strapi.destroy();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
};
describe('Content Manager - Hide relations', () => {
beforeAll(async () => {
await builder.addContentTypes([form.article]).build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Hide relations', async () => {
await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'PUT',
body: {
layouts: {
edit: [],
editRelations: [],
list: [],
},
},
});
const { body } = await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'GET',
});
expect(body.data.contentType.layouts.editRelations).toStrictEqual([]);
});
test('Hide relations after server restart', async () => {
await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'PUT',
body: {
layouts: {
edit: [],
editRelations: [],
list: [],
},
},
});
await restart();
const { body } = await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'GET',
});
expect(body.data.contentType.layouts.editRelations).toStrictEqual([]);
});
});

View File

@ -1,196 +0,0 @@
'use strict';
// Test a simple default API with no relations
const { omit, pick } = require('lodash/fp');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
const data = {
products: [],
shops: [],
};
const productModel = {
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
description: '',
collectionName: '',
};
const productWithDPModel = {
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
draftAndPublish: true,
description: '',
collectionName: '',
};
const shopModel = {
attributes: {
name: {
type: 'string',
},
products: {
type: 'relation',
relation: 'manyToMany',
target: 'api::product.product',
targetAttribute: 'shops',
},
},
displayName: 'Shop',
singularName: 'shop',
pluralName: 'shops',
};
const shops = [
{
name: 'market',
},
];
const products =
({ withPublished = false }) =>
({ shop }) => {
const shops = [shop[0].id];
const entries = [
{
name: 'tomato',
shops,
publishedAt: new Date(),
},
{
name: 'apple',
shops,
publishedAt: null,
},
];
if (withPublished) {
return entries;
}
return entries.map(omit('publishedAt'));
};
describe('Relation-list route', () => {
describe('without draftAndPublish', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productModel, shopModel])
.addFixtures(shopModel.singularName, shops)
.addFixtures(productModel.singularName, products({ withPublished: false }))
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
data.shops = await builder.sanitizedFixturesFor(shopModel.singularName, strapi);
data.products = await builder.sanitizedFixturesFor(productModel.singularName, strapi);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Can get relation-list for products of a shop', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.body).toHaveLength(data.products.length);
data.products.forEach((product, index) => {
expect(res.body[index]).toStrictEqual(pick(['_id', 'id', 'name'], product));
});
});
test('Can get relation-list for products of a shop and omit some results', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
body: {
idsToOmit: [data.products[0].id],
},
});
expect(res.body).toHaveLength(1);
expect(res.body[0]).toStrictEqual(pick(['_id', 'id', 'name'], data.products[1]));
});
});
describe('with draftAndPublish', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productWithDPModel, shopModel])
.addFixtures(shopModel.singularName, shops)
.addFixtures(productWithDPModel.singularName, products({ withPublished: true }))
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
data.shops = await builder.sanitizedFixturesFor(shopModel.singularName, strapi);
data.products = await builder.sanitizedFixturesFor(productWithDPModel.singularName, strapi);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Can get relation-list for products of a shop', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.body).toHaveLength(data.products.length);
const tomatoProductRes = res.body.find((p) => p.name === 'tomato');
const appleProductRes = res.body.find((p) => p.name === 'apple');
expect(tomatoProductRes).toMatchObject(pick(['_id', 'id', 'name'], data.products[0]));
expect(tomatoProductRes.publishedAt).toBeISODate();
expect(appleProductRes).toStrictEqual({
...pick(['_id', 'id', 'name'], data.products[1]),
publishedAt: null,
});
});
test('Can get relation-list for products of a shop and omit some results', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
body: {
idsToOmit: [data.products[1].id],
},
});
expect(res.body).toHaveLength(1);
expect(res.body[0]).toMatchObject(pick(['_id', 'id', 'name'], data.products[0]));
});
});
});

View File

@ -18,7 +18,7 @@ const createUserBodySchema = yup.object().shape({
connect: yup
.array()
.of(yup.object().shape({ id: yup.strapiID().required() }))
.min(1)
.min(1, 'Users must have a role')
.required(),
})
.required()
@ -36,7 +36,16 @@ const updateUserBodySchema = yup.object().shape({
connect: yup
.array()
.of(yup.object().shape({ id: yup.strapiID().required() }))
.min(1)
.required(),
disconnect: yup
.array()
.test('CheckDisconnect', 'Cannot remove role', function test(disconnectValue) {
if (value.connect.length === 0 && disconnectValue.length > 0) {
return false;
}
return true;
})
.required(),
})
: yup.strapiID()