feat: remove duplication of config in tests

This commit is contained in:
Dan Ribbens
2022-07-15 14:11:16 -04:00
parent a664b627f6
commit 40b6afff2d
21 changed files with 16 additions and 476 deletions

View File

@@ -1,12 +0,0 @@
@import '../../../../scss/styles';
.action-panel {
&__remove-row {
margin: 0 0 base(.3);
}
&__add-row {
margin: base(.3) 0 0;
}
}

View File

@@ -1,111 +0,0 @@
import React from 'react';
import Button from '../../../elements/Button';
import Popup from '../../../elements/Popup';
import BlockSelector from '../../field-types/Blocks/BlockSelector';
import { Props } from './types';
import './index.scss';
const baseClass = 'action-panel';
const ActionPanel: React.FC<Props> = (props) => {
const {
addRow,
removeRow,
label = 'Row',
blockType,
blocks = [],
rowIndex,
isHovered,
hasMaxRows,
} = props;
const classes = [
baseClass,
].filter(Boolean).join(' ');
return (
<div className={classes}>
<Popup
showOnHover
size="wide"
color="dark"
horizontalAlign="right"
buttonType="custom"
button={(
<Button
className={`${baseClass}__remove-row`}
round
buttonStyle="none"
icon="x"
iconPosition="left"
iconStyle="with-border"
onClick={() => removeRow(rowIndex)}
/>
)}
>
Remove&nbsp;
{label}
</Popup>
{!hasMaxRows && (
<React.Fragment>
{blockType === 'blocks'
? (
<Popup
buttonType="custom"
size="large"
horizontalAlign="right"
button={(
<Button
className={`${baseClass}__add-row`}
round
buttonStyle="none"
icon="plus"
iconPosition="left"
iconStyle="with-border"
/>
)}
render={({ close }) => (
<BlockSelector
blocks={blocks}
addRow={addRow}
addRowIndex={rowIndex}
close={close}
parentIsHovered={isHovered}
watchParentHover
/>
)}
/>
)
: (
<Popup
showOnHover
size="wide"
color="dark"
horizontalAlign="center"
buttonType="custom"
button={(
<Button
className={`${baseClass}__add-row`}
round
buttonStyle="none"
icon="plus"
iconPosition="left"
iconStyle="with-border"
onClick={() => addRow(rowIndex)}
/>
)}
>
Add&nbsp;
{label}
</Popup>
)}
</React.Fragment>
)}
</div>
);
};
export default ActionPanel;

View File

@@ -1,12 +0,0 @@
import { Block } from '../../../../../fields/config/types';
export type Props = {
label: string
addRow?: (index: number, blockType?: string) => void
removeRow?: (index: number) => void
blockType?: string
isHovered: boolean
rowIndex: number
blocks?: Block[]
hasMaxRows?: boolean
}

View File

@@ -1,21 +0,0 @@
@import '../../../../scss/styles';
.position-panel {
&__move-backward {
transform: rotate(.5turn);
margin: 0;
opacity: 0;
}
&__move-forward {
margin: 0;
opacity: 0;
}
&__current-position {
text-align: center;
color: var(--theme-elevation-400);
}
padding-right: base(.5);
}

View File

@@ -1,53 +0,0 @@
import React from 'react';
import Button from '../../../elements/Button';
import { Props } from './types';
import './index.scss';
const baseClass = 'position-panel';
const PositionPanel: React.FC<Props> = (props) => {
const { moveRow, positionIndex, rowCount, readOnly } = props;
const adjustedIndex = positionIndex + 1;
const classes = [
baseClass,
`${baseClass}__${readOnly ? 'read-only' : ''}`,
].filter(Boolean).join(' ');
return (
<div className={classes}>
{!readOnly && (
<Button
className={`${baseClass}__move-backward ${positionIndex === 0 ? 'first-row' : ''}`}
buttonStyle="none"
icon="chevron"
round
onClick={() => moveRow(positionIndex, positionIndex - 1)}
/>
)}
{(adjustedIndex && typeof positionIndex === 'number')
&& (
<div
className={`${baseClass}__current-position`}
>
{adjustedIndex >= 10 ? adjustedIndex : `0${adjustedIndex}`}
</div>
)}
{!readOnly && (
<Button
className={`${baseClass}__move-forward ${(positionIndex === rowCount - 1) ? 'last-row' : ''}`}
buttonStyle="none"
icon="chevron"
round
onClick={() => moveRow(positionIndex, positionIndex + 1)}
/>
)}
</div>
);
};
export default PositionPanel;

View File

@@ -1,6 +0,0 @@
export type Props = {
moveRow: (fromIndex: number, toIndex: number) => void
positionIndex: number
rowCount: number
readOnly: boolean
}

View File

@@ -1,100 +0,0 @@
@import '../../../scss/styles.scss';
//////////////////////
// COMPONENT STYLES
//////////////////////
.draggable-section {
padding-bottom: base(.5);
.draggable-section {
padding-bottom: 0;
}
&__content-wrapper {
display: flex;
position: relative;
margin-bottom: $baseline;
}
&__section-header {
display: flex;
position: sticky;
top: $top-header-offset;
z-index: 1;
padding: base(.75) base(.75);
margin-left: - base(.75);
margin-right: - base(.75);
width: calc(100% + #{base(1.5)});
}
&__render-fields-wrapper {
flex-grow: 1;
}
&.is-hovered>div {
>.field-type-gutter {
&.actions {
.field-type-gutter__content {
&:hover {
z-index: $z-nav;
}
}
.field-type-gutter__content-container {
box-shadow: none;
}
}
.field-type-gutter__content-container {
box-shadow: #{$style-stroke-width-m} 0px 0px 0px var(--theme-elevation-800);
}
.position-panel__move-forward,
.position-panel__move-backward {
opacity: 1;
&.first-row,
&.last-row {
opacity: .15;
pointer-events: none;
}
}
.position-panel__current-position {
color: var(--theme-elevation-800);
}
}
.toggle-collapse {
@include color-svg(var(--theme-elevation-0));
.btn__icon {
background-color: var(--theme-elevation-400);
&:hover {
background-color: var(--theme-elevation-800);
}
}
}
}
label.field-label {
line-height: 1;
padding-bottom: base(.75)
}
@include mid-break {
min-width: base(16);
.position-panel__move-forward,
.position-panel__move-backward {
opacity: 1;
}
&__section-header {
top: $top-header-offset-m;
}
}
}

View File

@@ -1,122 +0,0 @@
import React, { useState } from 'react';
import AnimateHeight from 'react-animate-height';
import { Draggable } from 'react-beautiful-dnd';
import ActionPanel from './ActionPanel';
import SectionTitle from './SectionTitle';
import PositionPanel from './PositionPanel';
import Button from '../../elements/Button';
import { NegativeFieldGutterProvider } from '../FieldTypeGutter/context';
import FieldTypeGutter from '../FieldTypeGutter';
import RenderFields from '../RenderFields';
import { Props } from './types';
import HiddenInput from '../field-types/HiddenInput';
import { fieldAffectsData } from '../../../../fields/config/types';
import './index.scss';
const baseClass = 'draggable-section';
const DraggableSection: React.FC<Props> = (props) => {
const {
moveRow,
addRow,
removeRow,
rowIndex,
rowCount,
parentPath,
fieldSchema,
label,
blockType,
fieldTypes,
id,
setRowCollapse,
isCollapsed,
permissions,
readOnly,
hasMaxRows,
} = props;
const [isHovered, setIsHovered] = useState(false);
const classes = [
baseClass,
isCollapsed ? 'is-collapsed' : 'is-open',
(isHovered && !readOnly) && 'is-hovered',
].filter(Boolean).join(' ');
return (
<Draggable
draggableId={id}
index={rowIndex}
isDragDisabled={readOnly}
>
{(providedDrag) => (
<div
ref={providedDrag.innerRef}
className={classes}
onMouseLeave={() => setIsHovered(false)}
onMouseOver={() => setIsHovered(true)}
onFocus={() => setIsHovered(true)}
{...providedDrag.draggableProps}
>
<div className={`${baseClass}__content-wrapper`}>
<FieldTypeGutter
variant="left"
dragHandleProps={providedDrag.dragHandleProps}
>
<PositionPanel
moveRow={moveRow}
rowCount={rowCount}
positionIndex={rowIndex}
readOnly={readOnly}
/>
</FieldTypeGutter>
<div className={`${baseClass}__render-fields-wrapper`}>
<AnimateHeight
height={isCollapsed ? 0 : 'auto'}
duration={200}
>
<NegativeFieldGutterProvider allow={false}>
<RenderFields
readOnly={readOnly}
fieldTypes={fieldTypes}
key={rowIndex}
permissions={permissions?.fields}
fieldSchema={fieldSchema.map((field) => ({
...field,
path: `${parentPath}.${rowIndex}${fieldAffectsData(field) ? `.${field.name}` : ''}`,
}))}
/>
</NegativeFieldGutterProvider>
</AnimateHeight>
</div>
<FieldTypeGutter
variant="right"
className="actions"
dragHandleProps={providedDrag.dragHandleProps}
>
{!readOnly && (
<ActionPanel
addRow={addRow}
removeRow={removeRow}
rowIndex={rowIndex}
label={label}
isHovered={isHovered}
hasMaxRows={hasMaxRows}
{...props}
/>
)}
</FieldTypeGutter>
</div>
</div>
)}
</Draggable>
);
};
export default DraggableSection;

View File

@@ -1,25 +0,0 @@
import { Field, Block } from '../../../../fields/config/types';
import { FieldTypes } from '../field-types';
import { FieldPermissions } from '../../../../auth/types';
export type Props = {
moveRow: (fromIndex: number, toIndex: number) => void
addRow: (index: number, blockType?: string) => void
removeRow: (index: number) => void
rowIndex: number
rowCount: number
parentPath: string
fieldSchema: Field[],
label?: string
blockType?: string
fieldTypes: FieldTypes
id: string
isCollapsed?: boolean
setRowCollapse?: (id: string, open: boolean) => void
positionPanelVerticalAlignment?: 'top' | 'center' | 'sticky'
actionPanelVerticalAlignment?: 'top' | 'center' | 'sticky'
permissions: FieldPermissions
readOnly: boolean
blocks?: Block[]
hasMaxRows?: boolean
}

View File

@@ -1,438 +0,0 @@
import fs from 'fs';
import path from 'path';
import FormData from 'form-data';
import { GraphQLClient } from 'graphql-request';
import { promisify } from 'util';
import getConfig from '../../config/load';
import { email, password } from '../../mongoose/testCredentials';
const stat = promisify(fs.stat);
require('isomorphic-fetch');
const config = getConfig();
const api = `${config.serverURL}${config.routes.api}`;
let client;
let token;
let headers;
describe('Collections - Uploads', () => {
beforeAll(async (done) => {
const response = await fetch(`${api}/admins/login`, {
body: JSON.stringify({
email,
password,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'post',
});
const data = await response.json();
({ token } = data);
headers = {
Authorization: `JWT ${token}`,
};
done();
});
describe('REST', () => {
const mediaDir = path.join(__dirname, '../../../demo', 'media');
beforeAll(async () => {
// Clear demo/media directory
const mediaDirExists = await fileExists(mediaDir);
if (!mediaDirExists) return;
fs.readdir(mediaDir, (err, files) => {
if (err) throw err;
// eslint-disable-next-line no-restricted-syntax
for (const file of files) {
fs.unlink(path.join(mediaDir, file), (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
}
});
});
describe('create', () => {
it('creates', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/image.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
// Check for files
expect(await fileExists(path.join(mediaDir, 'image.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'image-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'image-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'image-640x480.png'))).toBe(true);
// Check api response
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'image.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'image-16x16.png',
width: 16,
height: 16,
},
mobile: {
filename: 'image-320x240.png',
width: 320,
height: 240,
},
tablet: {
filename: 'image-640x480.png',
width: 640,
height: 480,
},
},
// We have a hook to check if upload sizes
// are properly bound to the Payload `req`.
// This field should be automatically set
// if they are found.
foundUploadSizes: true,
},
});
});
it('creates images that do not require all sizes', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/small.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
expect(await fileExists(path.join(mediaDir, 'small.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'small-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'small-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'small-640x480.png'))).toBe(false);
// Check api response
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'small.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'small-16x16.png',
width: 16,
height: 16,
},
},
// We have a hook to check if upload sizes
// are properly bound to the Payload `req`.
// This field should be automatically set
// if they are found.
foundUploadSizes: true,
},
});
});
it('creates media without storing a file', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/image.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/unstored-media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
// Check for files
expect(await !fileExists(path.join(mediaDir, 'image.png'))).toBe(false);
expect(await !fileExists(path.join(mediaDir, 'image-640x480.png'))).toBe(false);
// Check api response
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'image.png',
mimeType: 'image/png',
sizes: {
tablet: {
filename: 'image-640x480.png',
width: 640,
height: 480,
},
},
},
});
});
it('creates with same name', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/samename.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const firstResponse = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
expect(firstResponse.status).toBe(201);
const sameForm = new FormData();
sameForm.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/samename.png')),
);
sameForm.append('alt', 'test media');
sameForm.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: sameForm as unknown as BodyInit,
headers,
method: 'post',
});
expect(response.status).toBe(201);
const data = await response.json();
// Check for files
expect(await fileExists(path.join(mediaDir, 'samename-1.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'samename-1-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'samename-1-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'samename-1-640x480.png'))).toBe(true);
expect(data).toMatchObject({
doc: {
alt: 'test media',
filename: 'samename-1.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'samename-1-16x16.png',
width: 16,
height: 16,
},
mobile: {
filename: 'samename-1-320x240.png',
width: 320,
height: 240,
},
tablet: {
filename: 'samename-1-640x480.png',
width: 640,
height: 480,
},
},
},
});
});
});
it('update', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/update.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const response = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
const updateFormData = new FormData();
const newAlt = 'my new alt';
updateFormData.append('filename', data.doc.filename);
updateFormData.append('alt', newAlt);
const updateResponse = await fetch(`${api}/media/${data.doc.id}`, {
body: updateFormData as unknown as BodyInit,
headers,
method: 'put',
});
const updateResponseData = await updateResponse.json();
expect(updateResponse.status).toBe(200);
// Check that files weren't affected
expect(await fileExists(path.join(mediaDir, 'update.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'update-16x16.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'update-320x240.png'))).toBe(true);
expect(await fileExists(path.join(mediaDir, 'update-640x480.png'))).toBe(true);
// Check api response
expect(updateResponseData).toMatchObject({
doc: {
alt: newAlt,
filename: 'update.png',
mimeType: 'image/png',
sizes: {
icon: {
filename: 'update-16x16.png',
width: 16,
height: 16,
},
mobile: {
filename: 'update-320x240.png',
width: 320,
height: 240,
},
tablet: {
filename: 'update-640x480.png',
width: 640,
height: 480,
},
maintainedAspectRatio: {},
},
},
});
});
it('delete', async () => {
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/delete.png')),
);
formData.append('alt', 'test media');
formData.append('locale', 'en');
const createResponse = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const createData = await createResponse.json();
expect(createResponse.status).toBe(201);
const docId = createData.doc.id;
const response = await fetch(`${api}/media/${docId}`, {
headers,
method: 'delete',
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data.id).toBe(docId);
const imageExists = await fileExists(path.join(mediaDir, 'delete.png'));
expect(imageExists).toBe(false);
});
});
describe('GraphQL', () => {
// graphql cannot submit formData to create files, we only need to test getting relationship data on upload fields
let media;
let image;
const alt = 'alt text';
beforeAll(async (done) => {
client = new GraphQLClient(`${api}${config.routes.graphQL}`, {
headers: { Authorization: `JWT ${token}` },
});
// create media using REST
const formData = new FormData();
formData.append(
'file',
fs.createReadStream(path.join(__dirname, '../../..', 'tests/api/assets/image.png')),
);
formData.append('alt', alt);
formData.append('locale', 'en');
const mediaResponse = await fetch(`${api}/media`, {
body: formData as unknown as BodyInit,
headers,
method: 'post',
});
const mediaData = await mediaResponse.json();
media = mediaData.doc;
// create image that relates to media
headers['Content-Type'] = 'application/json';
const imageResponse = await fetch(`${api}/images`, {
body: JSON.stringify({
upload: media.id,
}),
headers,
method: 'post',
});
const data = await imageResponse.json();
image = data.doc;
done();
});
it('should query uploads relationship fields', async () => {
// language=graphQL
const query = `query {
Image(id: "${image.id}") {
id
upload {
alt
}
}
}`;
const response = await client.request(query);
expect(response.Image.upload.alt).toStrictEqual(alt);
});
});
});
async function fileExists(fileName: string): Promise<boolean> {
try {
await stat(fileName);
return true;
} catch (err) {
return false;
}
}