* fix: css selectors

This commit is contained in:
Dan Ribbens
2022-07-14 17:00:34 -04:00
committed by GitHub
parent 31bc4c6532
commit 48700a93e3
31 changed files with 168 additions and 148 deletions

View File

@@ -24,11 +24,14 @@ const Table: React.FC<Props> = ({ columns, data }) => {
</thead> </thead>
<tbody> <tbody>
{data && data.map((row, rowIndex) => ( {data && data.map((row, rowIndex) => (
<tr key={rowIndex}> <tr
key={rowIndex}
className={`row-${rowIndex + 1}`}
>
{columns.map((col, colIndex) => ( {columns.map((col, colIndex) => (
<td <td
key={colIndex} key={colIndex}
className={col.accessor} className={`cell-${col.accessor}`}
> >
{col.components.renderCell(row, row[col.accessor])} {col.components.renderCell(row, row[col.accessor])}
</td> </td>

View File

@@ -131,6 +131,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
return ( return (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<div <div
id={`field-${path}`}
className={classes} className={classes}
> >
<div className={`${baseClass}__error-wrap`}> <div className={`${baseClass}__error-wrap`}>

View File

@@ -181,6 +181,7 @@ const Blocks: React.FC<Props> = (props) => {
return ( return (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<div <div
id={`field-${path}`}
className={classes} className={classes}
> >
<div className={`${baseClass}__error-wrap`}> <div className={`${baseClass}__error-wrap`}>

View File

@@ -70,9 +70,9 @@ const Checkbox: React.FC<Props> = (props) => {
/> />
</div> </div>
<input <input
id={`field-${path}`}
type="checkbox" type="checkbox"
name={path} name={path}
id={path}
checked={Boolean(value)} checked={Boolean(value)}
readOnly readOnly
/> />

View File

@@ -78,11 +78,12 @@ const Code: React.FC<Props> = (props) => {
message={errorMessage} message={errorMessage}
/> />
<Label <Label
htmlFor={path} htmlFor={`field-${path}`}
label={label} label={label}
required={required} required={required}
/> />
<Editor <Editor
id={`field-${path}`}
value={value as string || ''} value={value as string || ''}
onValueChange={readOnly ? () => null : setValue} onValueChange={readOnly ? () => null : setValue}
highlight={highlighter} highlight={highlighter}

View File

@@ -43,7 +43,7 @@ const ConfirmPassword: React.FC = () => {
message={errorMessage} message={errorMessage}
/> />
<Label <Label
htmlFor="confirm-password" htmlFor="field-confirm-password"
label="Confirm Password" label="Confirm Password"
required required
/> />
@@ -52,7 +52,7 @@ const ConfirmPassword: React.FC = () => {
onChange={setValue} onChange={setValue}
type="password" type="password"
autoComplete="off" autoComplete="off"
id="confirm-password" id="field-confirm-password"
name="confirm-password" name="confirm-password"
/> />
</div> </div>

View File

@@ -76,7 +76,10 @@ const DateTime: React.FC<Props> = (props) => {
label={label} label={label}
required={required} required={required}
/> />
<div className={`${baseClass}__input-wrapper`}> <div
className={`${baseClass}__input-wrapper`}
id={`field-${path}`}
>
<DatePicker <DatePicker
{...date} {...date}
placeholder={placeholder} placeholder={placeholder}

View File

@@ -70,16 +70,16 @@ const Email: React.FC<Props> = (props) => {
/> />
<Label <Label
htmlFor={path} htmlFor={path}
label={label} label={`field-${path}`}
required={required} required={required}
/> />
<input <input
id={`field-${path}`}
value={value as string || ''} value={value as string || ''}
onChange={setValue} onChange={setValue}
disabled={Boolean(readOnly)} disabled={Boolean(readOnly)}
placeholder={placeholder} placeholder={placeholder}
type="email" type="email"
id={path}
name={path} name={path}
autoComplete={autoComplete} autoComplete={autoComplete}
/> />

View File

@@ -33,6 +33,7 @@ const Group: React.FC<Props> = (props) => {
return ( return (
<div <div
id={`field-${path}`}
className={[ className={[
'field-type', 'field-type',
baseClass, baseClass,

View File

@@ -25,6 +25,7 @@ const HiddenInput: React.FC<Props> = (props) => {
return ( return (
<input <input
id={`field-${path}`}
type="hidden" type="hidden"
value={value as string || ''} value={value as string || ''}
onChange={setValue} onChange={setValue}

View File

@@ -79,17 +79,17 @@ const NumberField: React.FC<Props> = (props) => {
message={errorMessage} message={errorMessage}
/> />
<Label <Label
htmlFor={path} htmlFor={`field-${path}`}
label={label} label={label}
required={required} required={required}
/> />
<input <input
id={`field-${path}`}
value={typeof value === 'number' ? value : ''} value={typeof value === 'number' ? value : ''}
onChange={handleChange} onChange={handleChange}
disabled={readOnly} disabled={readOnly}
placeholder={placeholder} placeholder={placeholder}
type="number" type="number"
id={path}
name={path} name={path}
step={step} step={step}
/> />

View File

@@ -60,17 +60,17 @@ const Password: React.FC<Props> = (props) => {
message={errorMessage} message={errorMessage}
/> />
<Label <Label
htmlFor={path} htmlFor={`field-${path}`}
label={label} label={label}
required={required} required={required}
/> />
<input <input
id={`field-${path}`}
value={value as string || ''} value={value as string || ''}
onChange={setValue} onChange={setValue}
disabled={formProcessing} disabled={formProcessing}
type="password" type="password"
autoComplete={autoComplete} autoComplete={autoComplete}
id={path}
name={path} name={path}
/> />
</div> </div>

View File

@@ -81,34 +81,34 @@ const PointField: React.FC<Props> = (props) => {
<ul className={`${baseClass}__wrap`}> <ul className={`${baseClass}__wrap`}>
<li> <li>
<Label <Label
htmlFor={`${path}.longitude`} htmlFor={`field-longitude-${path}`}
label={`${label} - Longitude`} label={`${label} - Longitude`}
required={required} required={required}
/> />
<input <input
id={`field-longitude-${path}`}
value={(value && typeof value[0] === 'number') ? value[0] : ''} value={(value && typeof value[0] === 'number') ? value[0] : ''}
onChange={(e) => handleChange(e, 0)} onChange={(e) => handleChange(e, 0)}
disabled={readOnly} disabled={readOnly}
placeholder={placeholder} placeholder={placeholder}
type="number" type="number"
id={`${path}.longitude`}
name={`${path}.longitude`} name={`${path}.longitude`}
step={step} step={step}
/> />
</li> </li>
<li> <li>
<Label <Label
htmlFor={`${path}.latitude`} htmlFor={`field-latitude-${path}`}
label={`${label} - Latitude`} label={`${label} - Latitude`}
required={required} required={required}
/> />
<input <input
id={`field-latitude-${path}`}
value={(value && typeof value[1] === 'number') ? value[1] : ''} value={(value && typeof value[1] === 'number') ? value[1] : ''}
onChange={(e) => handleChange(e, 1)} onChange={(e) => handleChange(e, 1)}
disabled={readOnly} disabled={readOnly}
placeholder={placeholder} placeholder={placeholder}
type="number" type="number"
id={`${path}.latitude`}
name={`${path}.latitude`} name={`${path}.latitude`}
step={step} step={step}
/> />

View File

@@ -73,11 +73,14 @@ const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
/> />
</div> </div>
<Label <Label
htmlFor={path} htmlFor={`field-${path}`}
label={label} label={label}
required={required} required={required}
/> />
<ul className={`${baseClass}--group`}> <ul
id={`field-${path}`}
className={`${baseClass}--group`}
>
{options.map((option) => { {options.map((option) => {
let optionValue = ''; let optionValue = '';

View File

@@ -13,7 +13,7 @@ const RadioInput: React.FC<Props> = (props) => {
isSelected && `${baseClass}--is-selected`, isSelected && `${baseClass}--is-selected`,
].filter(Boolean).join(' '); ].filter(Boolean).join(' ');
const id = `${path}-${option.value}`; const id = `field-${path}-${option.value}`;
return ( return (
<label <label

View File

@@ -335,6 +335,7 @@ const Relationship: React.FC<Props> = (props) => {
return ( return (
<div <div
id={`field-${path}`}
className={classes} className={classes}
style={{ style={{
...style, ...style,

View File

@@ -209,7 +209,7 @@ const RichText: React.FC<Props> = (props) => {
message={errorMessage} message={errorMessage}
/> />
<Label <Label
htmlFor={path} htmlFor={`field-${path}`}
label={label} label={label}
required={required} required={required}
/> />
@@ -270,6 +270,7 @@ const RichText: React.FC<Props> = (props) => {
ref={editorRef} ref={editorRef}
> >
<Editable <Editable
id={`field-${path}`}
className={`${baseClass}__input`} className={`${baseClass}__input`}
renderElement={renderElement} renderElement={renderElement}
renderLeaf={renderLeaf} renderLeaf={renderLeaf}

View File

@@ -62,6 +62,7 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
return ( return (
<div <div
id={`field-${path}`}
className={classes} className={classes}
style={{ style={{
...style, ...style,

View File

@@ -61,17 +61,17 @@ const TextInput: React.FC<TextInputProps> = (props) => {
message={errorMessage} message={errorMessage}
/> />
<Label <Label
htmlFor={path} htmlFor={`field-${path}`}
label={label} label={label}
required={required} required={required}
/> />
<input <input
id={`field-${path}`}
value={value || ''} value={value || ''}
onChange={onChange} onChange={onChange}
disabled={readOnly} disabled={readOnly}
placeholder={placeholder} placeholder={placeholder}
type="text" type="text"
id={path}
name={path} name={path}
/> />
<FieldDescription <FieldDescription

View File

@@ -62,16 +62,16 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
message={errorMessage} message={errorMessage}
/> />
<Label <Label
htmlFor={path} htmlFor={`field-${path}`}
label={label} label={label}
required={required} required={required}
/> />
<textarea <textarea
id={`field-${path}`}
value={value || ''} value={value || ''}
onChange={onChange} onChange={onChange}
disabled={readOnly} disabled={readOnly}
placeholder={placeholder} placeholder={placeholder}
id={path}
name={path} name={path}
rows={rows} rows={rows}
/> />

View File

@@ -7,6 +7,7 @@ import './index.scss';
type Value = { relationTo: string, value: number | string }; type Value = { relationTo: string, value: number | string };
const baseClass = 'relationship-cell'; const baseClass = 'relationship-cell';
const totalToShow = 3;
const RelationshipCell = (props) => { const RelationshipCell = (props) => {
const { field, data: cellData } = props; const { field, data: cellData } = props;
@@ -23,7 +24,7 @@ const RelationshipCell = (props) => {
const formattedValues: Value[] = []; const formattedValues: Value[] = [];
const arrayCellData = Array.isArray(cellData) ? cellData : [cellData]; const arrayCellData = Array.isArray(cellData) ? cellData : [cellData];
arrayCellData.slice(0, (arrayCellData.length < 3 ? arrayCellData.length : 3)).forEach((cell) => { arrayCellData.slice(0, (arrayCellData.length < totalToShow ? arrayCellData.length : totalToShow)).forEach((cell) => {
if (typeof cell === 'object' && 'relationTo' in cell && 'value' in cell) { if (typeof cell === 'object' && 'relationTo' in cell && 'value' in cell) {
formattedValues.push(cell); formattedValues.push(cell);
} }
@@ -50,16 +51,17 @@ const RelationshipCell = (props) => {
const relatedCollection = collections.find(({ slug }) => slug === relationTo); const relatedCollection = collections.find(({ slug }) => slug === relationTo);
return ( return (
<React.Fragment key={i}> <React.Fragment key={i}>
{document && document[relatedCollection.admin.useAsTitle] ? document[relatedCollection.admin.useAsTitle] : `Untitled - ID: ${value}`} { document === false && `Untitled - ID: ${value}`}
{ document === null && 'Loading...'}
{ document && (
document[relatedCollection.admin.useAsTitle] ? document[relatedCollection.admin.useAsTitle] : `Untitled - ID: ${value}`
)}
{values.length > i + 1 && ', '} {values.length > i + 1 && ', '}
</React.Fragment> </React.Fragment>
); );
})} })}
{!cellData && !values && hasRequested && ( { Array.isArray(cellData) && cellData.length > totalToShow && ` and ${cellData.length - totalToShow} more` }
<React.Fragment> { values.length === 0 && `No <${field.label}>`}
{`No <${field.label}>`}
</React.Fragment>
)}
</div> </div>
); );
}; };

View File

@@ -1,47 +0,0 @@
import React, { useEffect, useState } from 'react';
import querystring from 'qs';
import { requests } from '../../../../../../../api';
import { useConfig } from '../../../../../../utilities/Config';
const UploadCell = ({ data, field }) => {
const [cell, setCell] = useState<string>();
const { routes } = useConfig();
useEffect(() => {
const fetchUpload = async () => {
const params = {
depth: 0,
limit: 1,
where: {
id: {
equals: data.id,
},
},
};
const url = `${routes.api}/${field.relationTo}`;
const query = querystring.stringify(params, { addQueryPrefix: true });
const request = await requests.get(`${url}${query}`);
const result = await request.json();
if (result?.docs.length === 1) {
setCell(result.docs[0].filename);
} else {
setCell(`Untitled - ${data}`);
}
};
fetchUpload();
// get the doc
}, [data, field.relationTo, routes.api]);
return (
<React.Fragment>
<span>
{ cell }
</span>
</React.Fragment>
);
};
export default UploadCell;

View File

@@ -7,7 +7,6 @@ import relationship from './Relationship';
import richText from './Richtext'; import richText from './Richtext';
import select from './Select'; import select from './Select';
import textarea from './Textarea'; import textarea from './Textarea';
import upload from './Upload';
export default { export default {
@@ -20,5 +19,5 @@ export default {
richText, richText,
select, select,
textarea, textarea,
upload, upload: relationship,
}; };

View File

@@ -1,14 +1,16 @@
import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef } from 'react'; import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef } from 'react';
import querystring from 'qs'; import querystring from 'qs';
import { useConfig } from '../../../../utilities/Config'; import { useConfig } from '../../../../utilities/Config';
import { requests } from '../../../../../api';
import { TypeWithID } from '../../../../../../collections/config/types'; import { TypeWithID } from '../../../../../../collections/config/types';
import { reducer } from './reducer'; import { reducer } from './reducer';
import useDebounce from '../../../../../hooks/useDebounce'; import useDebounce from '../../../../../hooks/useDebounce';
// documents are first set to null when requested
// set to false when no doc is returned
// or set to the document returned
export type Documents = { export type Documents = {
[slug: string]: { [slug: string]: {
[id: string | number]: TypeWithID | null | 'loading' [id: string | number]: TypeWithID | null | false
} }
} }
@@ -32,7 +34,7 @@ export const RelationshipProvider: React.FC<{children?: React.ReactNode}> = ({ c
} = config; } = config;
useEffect(() => { useEffect(() => {
Object.entries(debouncedDocuments).forEach(([slug, docs]) => { Object.entries(debouncedDocuments).forEach(async ([slug, docs]) => {
const idsToLoad: (string | number)[] = []; const idsToLoad: (string | number)[] = [];
Object.entries(docs).forEach(([id, value]) => { Object.entries(docs).forEach(([id, value]) => {
@@ -50,12 +52,15 @@ export const RelationshipProvider: React.FC<{children?: React.ReactNode}> = ({ c
}; };
const query = querystring.stringify(params, { addQueryPrefix: true }); const query = querystring.stringify(params, { addQueryPrefix: true });
requests.get(`${url}${query}`).then(async (res) => { const result = await fetch(`${url}${query}`);
const result = await res.json(); if (result.ok) {
if (result.docs) { const json = await result.json();
dispatchDocuments({ type: 'ADD_LOADED', docs: result.docs, relationTo: slug }); if (json.docs) {
dispatchDocuments({ type: 'ADD_LOADED', docs: json.docs, relationTo: slug, idsToLoad });
}
} else {
dispatchDocuments({ type: 'ADD_LOADED', docs: [], relationTo: slug, idsToLoad });
} }
});
} }
}); });
}, [serverURL, api, debouncedDocuments]); }, [serverURL, api, debouncedDocuments]);

View File

@@ -10,6 +10,7 @@ type AddLoadedDocuments = {
type: 'ADD_LOADED', type: 'ADD_LOADED',
relationTo: string, relationTo: string,
docs: TypeWithID[], docs: TypeWithID[],
idsToLoad: (string | number)[]
} }
type Action = RequestDocuments | AddLoadedDocuments; type Action = RequestDocuments | AddLoadedDocuments;
@@ -34,13 +35,19 @@ export function reducer(state: Documents, action: Action): Documents {
if (typeof newState[action.relationTo] !== 'object') { if (typeof newState[action.relationTo] !== 'object') {
newState[action.relationTo] = {}; newState[action.relationTo] = {};
} }
const unreturnedIDs = [...action.idsToLoad];
if (Array.isArray(action.docs)) { if (Array.isArray(action.docs)) {
action.docs.forEach((doc) => { action.docs.forEach((doc) => {
unreturnedIDs.splice(unreturnedIDs.indexOf(doc.id), 1);
newState[action.relationTo][doc.id] = doc; newState[action.relationTo][doc.id] = doc;
}); });
} }
unreturnedIDs.forEach((id) => {
newState[action.relationTo][id] = false;
});
return newState; return newState;
} }

View File

@@ -58,6 +58,7 @@ const buildColumns = (collection: SanitizedCollectionConfig, columns: string[]):
), ),
renderCell: (rowData, cellData) => ( renderCell: (rowData, cellData) => (
<Cell <Cell
key={JSON.stringify(cellData)}
field={field} field={field}
colIndex={colIndex} colIndex={colIndex}
collection={collection} collection={collection}

View File

@@ -1,4 +1,3 @@
import { UploadedFile } from 'express-fileupload';
import { Payload } from '../../..'; import { Payload } from '../../..';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest } from '../../../express/types';
import { Document } from '../../../types'; import { Document } from '../../../types';

View File

@@ -1,6 +1,7 @@
import type { CollectionConfig } from '../../../src/collections/config/types'; import type { CollectionConfig } from '../../../src/collections/config/types';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
import { devUser } from '../../credentials'; import { devUser } from '../../credentials';
import { mapAsync } from '../../../src/utilities/mapAsync';
export const slug = 'fields-relationship'; export const slug = 'fields-relationship';
@@ -13,6 +14,7 @@ export interface FieldsRelationship {
id: string; id: string;
relationship: RelationOne; relationship: RelationOne;
relationshipHasMany: RelationOne[]; relationshipHasMany: RelationOne[];
relationshipHasManyMultiple: Array<RelationOne | RelationTwo | { relationTo: string, value: string}>;
relationshipMultiple: Array<RelationOne | RelationTwo>; relationshipMultiple: Array<RelationOne | RelationTwo>;
relationshipRestricted: RelationRestricted; relationshipRestricted: RelationRestricted;
relationshipWithTitle: RelationWithTitle; relationshipWithTitle: RelationWithTitle;
@@ -40,7 +42,7 @@ export default buildConfig({
{ {
slug, slug,
admin: { admin: {
defaultColumns: ['relationship', 'relationshipRestricted', 'with-existing-relations'], defaultColumns: ['id', 'relationship', 'relationshipRestricted', 'relationshipHasManyMultiple', 'relationshipWithTitle'],
}, },
fields: [ fields: [
{ {
@@ -59,6 +61,12 @@ export default buildConfig({
name: 'relationshipMultiple', name: 'relationshipMultiple',
relationTo: [relationOneSlug, relationTwoSlug], relationTo: [relationOneSlug, relationTwoSlug],
}, },
{
type: 'relationship',
name: 'relationshipHasManyMultiple',
hasMany: true,
relationTo: [relationOneSlug, relationTwoSlug],
},
{ {
type: 'relationship', type: 'relationship',
name: 'relationshipRestricted', name: 'relationshipRestricted',
@@ -113,19 +121,27 @@ export default buildConfig({
}, },
}); });
await payload.create<RelationOne>({ const relationOneIDs = [];
await mapAsync([...Array(5)], async () => {
const doc = await payload.create<RelationOne>({
collection: relationOneSlug, collection: relationOneSlug,
data: { data: {
name: relationOneSlug, name: relationOneSlug,
}, },
}); });
relationOneIDs.push(doc.id);
});
await payload.create<RelationTwo>({ const relationTwoIDs = [];
await mapAsync([...Array(11)], async () => {
const doc = await payload.create<RelationTwo>({
collection: relationTwoSlug, collection: relationTwoSlug,
data: { data: {
name: relationTwoSlug, name: relationTwoSlug,
}, },
}); });
relationTwoIDs.push(doc.id);
});
// Existing relationships // Existing relationships
const { id: restrictedDocId } = await payload.create<RelationRestricted>({ const { id: restrictedDocId } = await payload.create<RelationRestricted>({
@@ -148,5 +164,28 @@ export default buildConfig({
relationshipWithTitle: relationWithTitleDocId, relationshipWithTitle: relationWithTitleDocId,
}, },
}); });
await mapAsync([...Array(11)], async () => {
await payload.create<FieldsRelationship>({
collection: slug,
data: {
relationship: relationOneDocId,
relationshipRestricted: restrictedDocId,
relationshipHasManyMultiple: relationOneIDs.map((id) => ({ relationTo: relationOneSlug, value: id })),
},
});
});
await mapAsync([...Array(15)], async () => {
const relationOneID = relationOneIDs[Math.floor(Math.random() * 10)];
const relationTwoID = relationTwoIDs[Math.floor(Math.random() * 10)];
await payload.create<FieldsRelationship>({
collection: slug,
data: {
relationship: relationOneDocId,
relationshipRestricted: restrictedDocId,
relationshipHasMany: [relationOneID],
relationshipHasManyMultiple: [{ relationTo: relationTwoSlug, value: relationTwoID }],
},
});
});
}, },
}); });

View File

@@ -104,10 +104,9 @@ describe('fields - relationship', () => {
test('should create relationship', async () => { test('should create relationship', async () => {
await page.goto(url.create); await page.goto(url.create);
const fields = page.locator('.render-fields >> .react-select'); const field = page.locator('#field-relationship');
const relationshipField = fields.nth(0);
await relationshipField.click({ delay: 100 }); await field.click({ delay: 100 });
const options = page.locator('.rs__option'); const options = page.locator('.rs__option');
@@ -115,7 +114,7 @@ describe('fields - relationship', () => {
// Select a relationship // Select a relationship
await options.nth(1).click(); await options.nth(1).click();
await expect(relationshipField).toContainText(relationOneDoc.id); await expect(field).toContainText(relationOneDoc.id);
await saveDocAndAssert(page); await saveDocAndAssert(page);
}); });
@@ -123,10 +122,9 @@ describe('fields - relationship', () => {
test('should create hasMany relationship', async () => { test('should create hasMany relationship', async () => {
await page.goto(url.create); await page.goto(url.create);
const fields = page.locator('.render-fields >> .react-select'); const field = page.locator('.field-relationshipHasMany');
const relationshipHasManyField = fields.nth(1);
await relationshipHasManyField.click({ delay: 100 }); await field.click({ delay: 100 });
const options = page.locator('.rs__option'); const options = page.locator('.rs__option');
@@ -134,16 +132,16 @@ describe('fields - relationship', () => {
// Add one relationship // Add one relationship
await options.locator(`text=${relationOneDoc.id}`).click(); await options.locator(`text=${relationOneDoc.id}`).click();
await expect(relationshipHasManyField).toContainText(relationOneDoc.id); await expect(field).toContainText(relationOneDoc.id);
await expect(relationshipHasManyField).not.toContainText(anotherRelationOneDoc.id); await expect(field).not.toContainText(anotherRelationOneDoc.id);
// Add second relationship // Add second relationship
await relationshipHasManyField.click({ delay: 100 }); await field.click({ delay: 100 });
await options.locator(`text=${anotherRelationOneDoc.id}`).click(); await options.locator(`text=${anotherRelationOneDoc.id}`).click();
await expect(relationshipHasManyField).toContainText(anotherRelationOneDoc.id); await expect(field).toContainText(anotherRelationOneDoc.id);
// No options left // No options left
await relationshipHasManyField.click({ delay: 100 }); await field.click({ delay: 100 });
await expect(page.locator('.rs__menu')).toHaveText('No options'); await expect(page.locator('.rs__menu')).toHaveText('No options');
await saveDocAndAssert(page); await saveDocAndAssert(page);
@@ -152,10 +150,9 @@ describe('fields - relationship', () => {
test('should create relations to multiple collections', async () => { test('should create relations to multiple collections', async () => {
await page.goto(url.create); await page.goto(url.create);
const fields = page.locator('.render-fields >> .react-select'); const field = page.locator('.field-relationshipMultiple');
const relationshipMultipleField = fields.nth(2);
await relationshipMultipleField.click({ delay: 100 }); await field.click({ delay: 100 });
const options = page.locator('.rs__option'); const options = page.locator('.rs__option');
@@ -163,12 +160,12 @@ describe('fields - relationship', () => {
// Add one relationship // Add one relationship
await options.locator(`text=${relationOneDoc.id}`).click(); await options.locator(`text=${relationOneDoc.id}`).click();
await expect(relationshipMultipleField).toContainText(relationOneDoc.id); await expect(field).toContainText(relationOneDoc.id);
// Add relationship of different collection // Add relationship of different collection
await relationshipMultipleField.click({ delay: 100 }); await field.click({ delay: 100 });
await options.locator(`text=${relationTwoDoc.id}`).click(); await options.locator(`text=${relationTwoDoc.id}`).click();
await expect(relationshipMultipleField).toContainText(relationTwoDoc.id); await expect(field).toContainText(relationTwoDoc.id);
await saveDocAndAssert(page); await saveDocAndAssert(page);
}); });
@@ -177,11 +174,10 @@ describe('fields - relationship', () => {
test('should highlight existing relationship', async () => { test('should highlight existing relationship', async () => {
await page.goto(url.edit(docWithExistingRelations.id)); await page.goto(url.edit(docWithExistingRelations.id));
const fields = page.locator('.render-fields >> .react-select'); const field = page.locator('#field-relationship');
const relationOneField = fields.nth(0);
// Check dropdown options // Check dropdown options
await relationOneField.click({ delay: 100 }); await field.click({ delay: 100 });
await expect(page.locator('.rs__option--is-selected')).toHaveCount(1); await expect(page.locator('.rs__option--is-selected')).toHaveCount(1);
await expect(page.locator('.rs__option--is-selected')).toHaveText(relationOneDoc.id); await expect(page.locator('.rs__option--is-selected')).toHaveText(relationOneDoc.id);
@@ -190,29 +186,31 @@ describe('fields - relationship', () => {
test('should show untitled ID on restricted relation', async () => { test('should show untitled ID on restricted relation', async () => {
await page.goto(url.edit(docWithExistingRelations.id)); await page.goto(url.edit(docWithExistingRelations.id));
const fields = page.locator('.render-fields >> .react-select'); const field = page.locator('#field-relationshipRestricted');
const restrictedRelationField = fields.nth(3);
// Check existing relationship has untitled ID // Check existing relationship has untitled ID
await expect(restrictedRelationField).toContainText(`Untitled - ID: ${restrictedRelation.id}`); await expect(field).toContainText(`Untitled - ID: ${restrictedRelation.id}`);
// Check dropdown options // Check dropdown options
await restrictedRelationField.click({ delay: 100 }); await field.click({ delay: 100 });
const options = page.locator('.rs__option'); const options = page.locator('.rs__option');
await expect(options).toHaveCount(2); // None + 1 Unitled ID await expect(options).toHaveCount(2); // None + 1 Unitled ID
}); });
// test.todo('should paginate within the dropdown');
// test.todo('should search within the relationship field');
test('should show useAsTitle on relation', async () => { test('should show useAsTitle on relation', async () => {
await page.goto(url.edit(docWithExistingRelations.id)); await page.goto(url.edit(docWithExistingRelations.id));
const fields = page.locator('.render-fields >> .react-select'); const field = page.locator('#field-relationshipWithTitle .react-select');
const relationWithTitleField = fields.nth(4);
// Check existing relationship for correct title // Check existing relationship for correct title
await expect(relationWithTitleField).toHaveText(relationWithTitle.name); await expect(field).toHaveText(relationWithTitle.name);
await page.screenshot({ path: './bad.png' });
await relationWithTitleField.click({ delay: 100 }); await field.click({ delay: 100 });
const options = page.locator('.rs__option'); const options = page.locator('.rs__option');
await expect(options).toHaveCount(2); // None + 1 Doc await expect(options).toHaveCount(2); // None + 1 Doc
@@ -220,26 +218,26 @@ describe('fields - relationship', () => {
test('should show id on relation in list view', async () => { test('should show id on relation in list view', async () => {
await page.goto(url.list); await page.goto(url.list);
await wait(1000); await wait(110);
const cells = page.locator('.relationship'); const cells = page.locator('.cell-relationship');
const relationship = cells.nth(0); const relationship = cells.nth(0);
await expect(relationship).toHaveText(relationOneDoc.id); await expect(relationship).toHaveText(relationOneDoc.id);
}); });
test('should show Untitled ID on restricted relation in list view', async () => {
await page.goto(url.list);
await wait(110);
const cells = page.locator('.cell-relationshipRestricted');
const relationship = cells.nth(0);
await expect(relationship).toContainText('Untitled - ID: ');
});
test('should show useAsTitle on relation in list view', async () => { test('should show useAsTitle on relation in list view', async () => {
await page.goto(url.list); await page.goto(url.list);
wait(110); await wait(110);
const cells = page.locator('.relationshipWithTitle'); const cells = page.locator('.cell-relationshipWithTitle');
const relationship = cells.nth(0); const relationship = cells.nth(0);
await expect(relationship).toHaveText(relationWithTitle.id); await expect(relationship).toHaveText(relationWithTitle.name);
});
test('should show untitled ID on restricted relation in list view', async () => {
await page.goto(url.list);
wait(110);
const cells = page.locator('.relationship');
const relationship = cells.nth(0);
await expect(relationship).toHaveText(relationOneDoc.id);
}); });
}); });
}); });

View File

@@ -17,9 +17,9 @@ export async function firstRegister(args: FirstRegisterArgs): Promise<void> {
const { page, serverURL } = args; const { page, serverURL } = args;
await page.goto(`${serverURL}/admin`); await page.goto(`${serverURL}/admin`);
await page.fill('#email', devUser.email); await page.fill('#field-email', devUser.email);
await page.fill('#password', devUser.password); await page.fill('#field-password', devUser.password);
await page.fill('#confirm-password', devUser.password); await page.fill('#field-confirm-password', devUser.password);
await wait(500); await wait(500);
await page.click('[type=submit]'); await page.click('[type=submit]');
await page.waitForURL(`${serverURL}/admin`); await page.waitForURL(`${serverURL}/admin`);
@@ -29,8 +29,8 @@ export async function login(args: LoginArgs): Promise<void> {
const { page, serverURL } = args; const { page, serverURL } = args;
await page.goto(`${serverURL}/admin`); await page.goto(`${serverURL}/admin`);
await page.fill('#email', devUser.email); await page.fill('#field-email', devUser.email);
await page.fill('#password', devUser.password); await page.fill('#field-password', devUser.password);
await wait(500); await wait(500);
await page.click('[type=submit]'); await page.click('[type=submit]');
await page.waitForURL(`${serverURL}/admin`); await page.waitForURL(`${serverURL}/admin`);

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB