feat: remove duplication of config in tests
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
@import '../../../../scss/styles';
|
||||
|
||||
.action-panel {
|
||||
|
||||
&__remove-row {
|
||||
margin: 0 0 base(.3);
|
||||
}
|
||||
|
||||
&__add-row {
|
||||
margin: base(.3) 0 0;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{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
|
||||
{label}
|
||||
</Popup>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionPanel;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -1,6 +0,0 @@
|
||||
export type Props = {
|
||||
moveRow: (fromIndex: number, toIndex: number) => void
|
||||
positionIndex: number
|
||||
rowCount: number
|
||||
readOnly: boolean
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user