mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: improve coverage of cypress (#5483)
This commit is contained in:
parent
3b72f90ca5
commit
b794f3894e
File diff suppressed because one or more lines are too long
@ -1 +1,80 @@
|
|||||||
[{"database_id":"037a985f-f369-4c4a-8011-620012850a68","created_at":"1713429700","views":["48c52cf7-bf98-43fa-96ad-b31aade9b071"]},{"database_id":"daea6aee-9365-4703-a8e2-a2fa6a07b214","created_at":"1714449533","views":["b6347acb-3174-4f0e-98e9-dcce07e5dbf7"]},{"database_id":"4c658817-20db-4f56-b7f9-0637a22dfeb6","created_at":"0","views":["7d2148fc-cace-4452-9c5c-96e52e6bf8b5","e410747b-5f2f-45a0-b2f7-890ad3001355","2143e95d-5dcb-4e0f-bb2c-50944e6e019f","a5566e49-f156-4168-9b2d-17926c5da329","135615fa-66f7-4451-9b54-d7e99445fca4","b4e77203-5c8b-48df-bbc5-2e1143eb0e61","a6af311f-cbc8-42c2-b801-7115619c3776"]},{"database_id":"4c658817-20db-4f56-b7f9-0637a22dfeb6","created_at":"0","views":["7d2148fc-cace-4452-9c5c-96e52e6bf8b5","e97877f5-c365-4025-9e6a-e590c4b19dbb","f0c59921-04ee-4971-995c-79b7fd8c00e2","7eb697cd-6a55-40bb-96ac-0d4a3bc924b2"]},{"database_id":"ee63da2b-aa2a-4d0b-aab0-59008635363a","created_at":"0","views":["2c1ee95a-1b09-4a1f-8d5e-501bc4861a9d","91ea7c08-f6b3-4b81-aa1e-d3664686186f"]},{"database_id":"e788f014-d0d3-4dfe-81ef-aa1ebb4d6366","created_at":"0","views":["1b0e322d-4909-4c63-914a-d034fc363097","350f425b-b671-4e2d-8182-5998a6e62924"]},{"database_id":"ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d","created_at":"0","views":["0ce13415-6cce-4497-94c6-475ad96c249e","e4c89421-12b2-4d02-863d-20949eec9271"]},{"database_id":"ce267d12-3b61-4ebb-bb03-d65272f5f817","created_at":"0","views":["ee3ae8ce-959a-4df3-8734-40b535ff88e3","66a6f3bc-c78f-4f74-a09e-08d4717bf1fd","2bf50c03-f41f-4363-b5b1-101216a6c5cc"]}]
|
[
|
||||||
|
{
|
||||||
|
"database_id": "037a985f-f369-4c4a-8011-620012850a68",
|
||||||
|
"created_at": "1713429700",
|
||||||
|
"views": [
|
||||||
|
"48c52cf7-bf98-43fa-96ad-b31aade9b071"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "daea6aee-9365-4703-a8e2-a2fa6a07b214",
|
||||||
|
"created_at": "1714449533",
|
||||||
|
"views": [
|
||||||
|
"b6347acb-3174-4f0e-98e9-dcce07e5dbf7"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "4c658817-20db-4f56-b7f9-0637a22dfeb6",
|
||||||
|
"created_at": "0",
|
||||||
|
"views": [
|
||||||
|
"7d2148fc-cace-4452-9c5c-96e52e6bf8b5",
|
||||||
|
"e410747b-5f2f-45a0-b2f7-890ad3001355",
|
||||||
|
"2143e95d-5dcb-4e0f-bb2c-50944e6e019f",
|
||||||
|
"a5566e49-f156-4168-9b2d-17926c5da329",
|
||||||
|
"135615fa-66f7-4451-9b54-d7e99445fca4",
|
||||||
|
"b4e77203-5c8b-48df-bbc5-2e1143eb0e61",
|
||||||
|
"a6af311f-cbc8-42c2-b801-7115619c3776"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "4c658817-20db-4f56-b7f9-0637a22dfeb6",
|
||||||
|
"created_at": "0",
|
||||||
|
"views": [
|
||||||
|
"7d2148fc-cace-4452-9c5c-96e52e6bf8b5",
|
||||||
|
"e97877f5-c365-4025-9e6a-e590c4b19dbb",
|
||||||
|
"f0c59921-04ee-4971-995c-79b7fd8c00e2",
|
||||||
|
"7eb697cd-6a55-40bb-96ac-0d4a3bc924b2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "ee63da2b-aa2a-4d0b-aab0-59008635363a",
|
||||||
|
"created_at": "0",
|
||||||
|
"views": [
|
||||||
|
"2c1ee95a-1b09-4a1f-8d5e-501bc4861a9d",
|
||||||
|
"91ea7c08-f6b3-4b81-aa1e-d3664686186f"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "e788f014-d0d3-4dfe-81ef-aa1ebb4d6366",
|
||||||
|
"created_at": "0",
|
||||||
|
"views": [
|
||||||
|
"1b0e322d-4909-4c63-914a-d034fc363097",
|
||||||
|
"350f425b-b671-4e2d-8182-5998a6e62924"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d",
|
||||||
|
"created_at": "0",
|
||||||
|
"views": [
|
||||||
|
"0ce13415-6cce-4497-94c6-475ad96c249e",
|
||||||
|
"e4c89421-12b2-4d02-863d-20949eec9271"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "ce267d12-3b61-4ebb-bb03-d65272f5f817",
|
||||||
|
"created_at": "0",
|
||||||
|
"views": [
|
||||||
|
"ee3ae8ce-959a-4df3-8734-40b535ff88e3",
|
||||||
|
"66a6f3bc-c78f-4f74-a09e-08d4717bf1fd",
|
||||||
|
"2bf50c03-f41f-4363-b5b1-101216a6c5cc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"database_id": "87bc006e-c1eb-47fd-9ac6-e39b17956369",
|
||||||
|
"created_at": "0",
|
||||||
|
"views": [
|
||||||
|
"7f233be4-1b4d-46b2-bcfc-f341b8d75267",
|
||||||
|
"a734a068-e73d-4b4b-853c-4daffea389c0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -36,12 +36,36 @@ declare global {
|
|||||||
mockCurrentWorkspace: () => void;
|
mockCurrentWorkspace: () => void;
|
||||||
mockGetWorkspaceDatabases: () => void;
|
mockGetWorkspaceDatabases: () => void;
|
||||||
mockDocument: (id: string) => void;
|
mockDocument: (id: string) => void;
|
||||||
|
clickOutside: () => void;
|
||||||
|
getTestingSelector: (testId: string) => Chainable<JQuery<HTMLElement>>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add('mount', mount);
|
Cypress.Commands.add('mount', mount);
|
||||||
|
|
||||||
|
Cypress.Commands.add('getTestingSelector', (testId: string) => {
|
||||||
|
return cy.get(`[data-testid="${testId}"]`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('clickOutside', () => {
|
||||||
|
cy.document().then((doc) => {
|
||||||
|
// [0, 0] is the top left corner of the window
|
||||||
|
const x = 0;
|
||||||
|
const y = 0;
|
||||||
|
|
||||||
|
const evt = new MouseEvent('click', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window,
|
||||||
|
clientX: x,
|
||||||
|
clientY: y,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dispatch the event
|
||||||
|
doc.elementFromPoint(x, y)?.dispatchEvent(evt);
|
||||||
|
});
|
||||||
|
});
|
||||||
// Example use:
|
// Example use:
|
||||||
// cy.mount(<MyComponent />)
|
// cy.mount(<MyComponent />)
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ViewLayout, YFolder, YjsFolderKey } from '@/application/collab.type';
|
import { ViewLayout, YFolder, YjsFolderKey } from '@/application/collab.type';
|
||||||
import { createContext, useCallback, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
export interface Crumb {
|
export interface Crumb {
|
||||||
@ -36,14 +36,3 @@ export const useNavigateToView = () => {
|
|||||||
export const useCrumbs = () => {
|
export const useCrumbs = () => {
|
||||||
return useContext(FolderContext)?.crumbs;
|
return useContext(FolderContext)?.crumbs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usePushCrumb = () => {
|
|
||||||
const { setCrumbs } = useContext(FolderContext) || {};
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
(crumb: Crumb) => {
|
|
||||||
setCrumbs?.((prevCrumbs) => [...prevCrumbs, crumb]);
|
|
||||||
},
|
|
||||||
[setCrumbs]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -100,6 +100,7 @@ export async function batchCollabs(
|
|||||||
|
|
||||||
const res = await batchFetchCollab(workspaceId, params);
|
const res = await batchFetchCollab(workspaceId, params);
|
||||||
|
|
||||||
|
console.log('Fetched collab data:', res);
|
||||||
for (const id of Object.keys(res)) {
|
for (const id of Object.keys(res)) {
|
||||||
const type = params.find((param) => param.object_id === id)?.collab_type;
|
const type = params.find((param) => param.object_id === id)?.collab_type;
|
||||||
const data = res[id];
|
const data = res[id];
|
||||||
|
@ -1,123 +0,0 @@
|
|||||||
import { YDoc, YFolder, YjsEditorKey } from '@/application/collab.type';
|
|
||||||
import { applyYDoc } from '@/application/ydoc/apply';
|
|
||||||
import { FolderProvider } from '@/components/_shared/context-provider/FolderProvider';
|
|
||||||
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
|
||||||
import withAppWrapper from '@/components/app/withAppWrapper';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Database } from './Database';
|
|
||||||
import { DatabaseContextProvider } from './DatabaseContext';
|
|
||||||
import * as Y from 'yjs';
|
|
||||||
import '@/components/layout/layout.scss';
|
|
||||||
|
|
||||||
describe('<Database />', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.viewport(1280, 720);
|
|
||||||
Object.defineProperty(window.navigator, 'language', { value: 'en-US' });
|
|
||||||
Object.defineProperty(window.navigator, 'languages', { value: ['en-US'] });
|
|
||||||
cy.mockDatabase();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders with a database', () => {
|
|
||||||
cy.fixture('folder').then((folderJson) => {
|
|
||||||
const doc = new Y.Doc();
|
|
||||||
const state = new Uint8Array(folderJson.data.doc_state);
|
|
||||||
|
|
||||||
applyYDoc(doc, state);
|
|
||||||
|
|
||||||
const folder = doc.getMap(YjsEditorKey.data_section).get(YjsEditorKey.folder) as YFolder;
|
|
||||||
|
|
||||||
cy.fixture(`database/4c658817-20db-4f56-b7f9-0637a22dfeb6`).then((database) => {
|
|
||||||
cy.fixture(`database/rows/4c658817-20db-4f56-b7f9-0637a22dfeb6`).then((rows) => {
|
|
||||||
const doc = new Y.Doc();
|
|
||||||
const rootRowsDoc = new Y.Doc();
|
|
||||||
const rowsFolder: Y.Map<YDoc> = rootRowsDoc.getMap();
|
|
||||||
const databaseState = new Uint8Array(database.data.doc_state);
|
|
||||||
|
|
||||||
applyYDoc(doc, databaseState);
|
|
||||||
|
|
||||||
Object.keys(rows).forEach((key) => {
|
|
||||||
const data = rows[key];
|
|
||||||
const rowDoc = new Y.Doc();
|
|
||||||
|
|
||||||
applyYDoc(rowDoc, new Uint8Array(data));
|
|
||||||
rowsFolder.set(key, rowDoc);
|
|
||||||
});
|
|
||||||
|
|
||||||
const onNavigateToView = cy.stub();
|
|
||||||
|
|
||||||
const AppWrapper = withAppWrapper(() => {
|
|
||||||
return (
|
|
||||||
<div className={'flex h-screen w-screen flex-col py-4'}>
|
|
||||||
<TestDatabase
|
|
||||||
databaseDoc={doc}
|
|
||||||
rows={rowsFolder}
|
|
||||||
folder={folder}
|
|
||||||
iidIndex={'7d2148fc-cace-4452-9c5c-96e52e6bf8b5'}
|
|
||||||
initialViewId={'7d2148fc-cace-4452-9c5c-96e52e6bf8b5'}
|
|
||||||
onNavigateToView={onNavigateToView}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.mount(<AppWrapper />);
|
|
||||||
|
|
||||||
cy.get('[data-testid^=view-tab-]').should('have.length', 4);
|
|
||||||
cy.get('.database-grid').should('exist');
|
|
||||||
|
|
||||||
cy.get('[data-testid=view-tab-e410747b-5f2f-45a0-b2f7-890ad3001355]').click();
|
|
||||||
cy.get('.database-board').should('exist');
|
|
||||||
cy.wrap(onNavigateToView).should('have.been.calledOnceWith', 'e410747b-5f2f-45a0-b2f7-890ad3001355');
|
|
||||||
|
|
||||||
cy.wait(800);
|
|
||||||
cy.get('[data-testid=view-tab-7d2148fc-cace-4452-9c5c-96e52e6bf8b5]').click();
|
|
||||||
cy.get('.database-grid').should('exist');
|
|
||||||
cy.wrap(onNavigateToView).should('have.been.calledWith', '7d2148fc-cace-4452-9c5c-96e52e6bf8b5');
|
|
||||||
|
|
||||||
cy.wait(800);
|
|
||||||
cy.get('[data-testid=view-tab-2143e95d-5dcb-4e0f-bb2c-50944e6e019f]').click();
|
|
||||||
cy.get('.database-calendar').should('exist');
|
|
||||||
cy.wrap(onNavigateToView).should('have.been.calledWith', '2143e95d-5dcb-4e0f-bb2c-50944e6e019f');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function TestDatabase({
|
|
||||||
databaseDoc,
|
|
||||||
rows,
|
|
||||||
folder,
|
|
||||||
iidIndex,
|
|
||||||
initialViewId,
|
|
||||||
onNavigateToView,
|
|
||||||
}: {
|
|
||||||
databaseDoc: YDoc;
|
|
||||||
rows: Y.Map<YDoc>;
|
|
||||||
folder: YFolder;
|
|
||||||
iidIndex: string;
|
|
||||||
initialViewId: string;
|
|
||||||
onNavigateToView: (viewId: string) => void;
|
|
||||||
}) {
|
|
||||||
const [activeViewId, setActiveViewId] = useState<string>(initialViewId);
|
|
||||||
|
|
||||||
const handleNavigateToView = (viewId: string) => {
|
|
||||||
setActiveViewId(viewId);
|
|
||||||
onNavigateToView(viewId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FolderProvider folder={folder}>
|
|
||||||
<IdProvider objectId={iidIndex}>
|
|
||||||
<DatabaseContextProvider
|
|
||||||
viewId={activeViewId || iidIndex}
|
|
||||||
databaseDoc={databaseDoc}
|
|
||||||
rowDocMap={rows}
|
|
||||||
readOnly={true}
|
|
||||||
>
|
|
||||||
<Database iidIndex={iidIndex} viewId={activeViewId} onNavigateToView={handleNavigateToView} />
|
|
||||||
</DatabaseContextProvider>
|
|
||||||
</IdProvider>
|
|
||||||
</FolderProvider>
|
|
||||||
);
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
import { renderDatabase } from '@/components/database/__tests__/withTestingDatabase';
|
||||||
|
import '@/components/layout/layout.scss';
|
||||||
|
|
||||||
|
describe('<Database />', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport(1280, 720);
|
||||||
|
Object.defineProperty(window.navigator, 'language', { value: 'en-US' });
|
||||||
|
cy.mockDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with a database', () => {
|
||||||
|
const onNavigateToView = cy.stub();
|
||||||
|
|
||||||
|
renderDatabase(
|
||||||
|
{
|
||||||
|
databaseId: '4c658817-20db-4f56-b7f9-0637a22dfeb6',
|
||||||
|
viewId: '7d2148fc-cace-4452-9c5c-96e52e6bf8b5',
|
||||||
|
onNavigateToView,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
cy.get('[data-testid^=view-tab-]').should('have.length', 4);
|
||||||
|
cy.get('.database-grid').should('exist');
|
||||||
|
|
||||||
|
cy.get('[data-testid=view-tab-e410747b-5f2f-45a0-b2f7-890ad3001355]').click();
|
||||||
|
cy.get('.database-board').should('exist');
|
||||||
|
cy.wrap(onNavigateToView).should('have.been.calledOnceWith', 'e410747b-5f2f-45a0-b2f7-890ad3001355');
|
||||||
|
|
||||||
|
cy.wait(800);
|
||||||
|
cy.get('[data-testid=view-tab-7d2148fc-cace-4452-9c5c-96e52e6bf8b5]').click();
|
||||||
|
cy.get('.database-grid').should('exist');
|
||||||
|
cy.wrap(onNavigateToView).should('have.been.calledWith', '7d2148fc-cace-4452-9c5c-96e52e6bf8b5');
|
||||||
|
|
||||||
|
cy.wait(800);
|
||||||
|
cy.get('[data-testid=view-tab-2143e95d-5dcb-4e0f-bb2c-50944e6e019f]').click();
|
||||||
|
cy.get('.database-calendar').should('exist');
|
||||||
|
cy.wrap(onNavigateToView).should('have.been.calledWith', '2143e95d-5dcb-4e0f-bb2c-50944e6e019f');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -3,8 +3,8 @@ import { applyYDoc } from '@/application/ydoc/apply';
|
|||||||
import { FolderProvider } from '@/components/_shared/context-provider/FolderProvider';
|
import { FolderProvider } from '@/components/_shared/context-provider/FolderProvider';
|
||||||
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
||||||
import withAppWrapper from '@/components/app/withAppWrapper';
|
import withAppWrapper from '@/components/app/withAppWrapper';
|
||||||
import { DatabaseRow } from './DatabaseRow';
|
import { DatabaseRow } from 'src/components/database/DatabaseRow';
|
||||||
import { DatabaseContextProvider } from './DatabaseContext';
|
import { DatabaseContextProvider } from 'src/components/database/DatabaseContext';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
import '@/components/layout/layout.scss';
|
import '@/components/layout/layout.scss';
|
||||||
|
|
@ -0,0 +1,99 @@
|
|||||||
|
import { renderDatabase } from '@/components/database/__tests__/withTestingDatabase';
|
||||||
|
import '@/components/layout/layout.scss';
|
||||||
|
|
||||||
|
describe('<Database /> with filters and sorts', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport(1280, 720);
|
||||||
|
Object.defineProperty(window.navigator, 'language', { value: 'en-US' });
|
||||||
|
cy.mockDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('render a database with filters and sorts', () => {
|
||||||
|
const onNavigateToView = cy.stub();
|
||||||
|
|
||||||
|
renderDatabase(
|
||||||
|
{
|
||||||
|
onNavigateToView,
|
||||||
|
databaseId: '87bc006e-c1eb-47fd-9ac6-e39b17956369',
|
||||||
|
viewId: '7f233be4-1b4d-46b2-bcfc-f341b8d75267',
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.getTestingSelector('database-actions-filter').click();
|
||||||
|
|
||||||
|
cy.get('.database-conditions').then(($el) => {
|
||||||
|
cy.wait(500);
|
||||||
|
const height = $el.height();
|
||||||
|
|
||||||
|
expect(height).to.be.greaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.getTestingSelector('database-sort-condition').click();
|
||||||
|
cy.wait(500);
|
||||||
|
cy.getTestingSelector('sort-condition').as('sortConditions').should('have.length', 2);
|
||||||
|
cy.get('@sortConditions').eq(0).contains('number');
|
||||||
|
cy.get('@sortConditions').eq(0).contains('Ascending');
|
||||||
|
cy.get('@sortConditions').eq(1).contains('Name');
|
||||||
|
cy.get('@sortConditions').eq(1).contains('Descending');
|
||||||
|
cy.clickOutside();
|
||||||
|
cy.getTestingSelector('sort-condition-list').should('not.exist');
|
||||||
|
|
||||||
|
// the length of filters should be 6
|
||||||
|
cy.getTestingSelector('database-filter-condition').as('filterConditions');
|
||||||
|
cy.get('@filterConditions').should('have.length', 6);
|
||||||
|
// the first filter should be 'Name', the value should be 'contains', and the input should be 123
|
||||||
|
cy.get('@filterConditions').eq(0).as('filterCondition');
|
||||||
|
cy.get('@filterCondition').contains('Name');
|
||||||
|
cy.get('@filterCondition').contains('123');
|
||||||
|
cy.get('@filterCondition').click();
|
||||||
|
cy.getTestingSelector('filter-menu-popover').should('be.visible');
|
||||||
|
cy.getTestingSelector('filter-condition-type').contains('Contains');
|
||||||
|
cy.get(`[data-testid="text-filter-input"] input`).should('have.value', '123');
|
||||||
|
cy.clickOutside();
|
||||||
|
// the second filter should be 'Type', the value should be 'is not empty'
|
||||||
|
cy.get('@filterConditions').eq(1).as('filterCondition');
|
||||||
|
cy.get('@filterCondition').contains('Type');
|
||||||
|
cy.get('@filterCondition').contains('is not empty');
|
||||||
|
cy.get('@filterCondition').click();
|
||||||
|
cy.clickOutside();
|
||||||
|
// the third filter should be 'Done', the value should be 'is Checked'
|
||||||
|
cy.get('@filterConditions').eq(2).as('filterCondition');
|
||||||
|
cy.get('@filterCondition').contains('Done');
|
||||||
|
cy.get('@filterCondition').contains('is Checked');
|
||||||
|
cy.get('@filterCondition').click();
|
||||||
|
cy.clickOutside();
|
||||||
|
// the fourth filter should be 'Number', the value should be 'is greater than', and the input should be 600
|
||||||
|
cy.get('@filterConditions').eq(3).as('filterCondition');
|
||||||
|
cy.get('@filterCondition').contains('number');
|
||||||
|
cy.get('@filterCondition').contains('> 600');
|
||||||
|
cy.get('@filterCondition').click();
|
||||||
|
cy.getTestingSelector('filter-menu-popover').should('be.visible');
|
||||||
|
cy.getTestingSelector('filter-condition-type').contains('Is greater than');
|
||||||
|
cy.get(`[data-testid="number-filter-input"] input`).should('have.value', '600');
|
||||||
|
cy.clickOutside();
|
||||||
|
// the fifth filter should be 'multi type', the value should be 'Does not contain'
|
||||||
|
cy.get('@filterConditions').eq(4).as('filterCondition');
|
||||||
|
cy.get('@filterCondition').contains('multi type');
|
||||||
|
cy.get('@filterCondition').click();
|
||||||
|
cy.getTestingSelector('filter-menu-popover').should('be.visible');
|
||||||
|
cy.getTestingSelector('filter-condition-type').contains('Does not contain');
|
||||||
|
cy.getTestingSelector('select-option-list').as('selectOptionList');
|
||||||
|
cy.get('@selectOptionList').should('have.length', 2);
|
||||||
|
cy.get('@selectOptionList').eq(0).contains('option-2');
|
||||||
|
cy.get('@selectOptionList').eq(1).contains('option-1');
|
||||||
|
cy.get('@selectOptionList').eq(1).should('have.data', 'checked', true);
|
||||||
|
cy.clickOutside();
|
||||||
|
// the sixth filter should be 'Checklist', the value should be 'is completed'
|
||||||
|
cy.get('@filterConditions').eq(5).as('filterCondition');
|
||||||
|
cy.get('@filterCondition').contains('Checklist');
|
||||||
|
cy.get('@filterCondition').contains('is complete');
|
||||||
|
cy.get('@filterCondition').click();
|
||||||
|
cy.clickOutside();
|
||||||
|
|
||||||
|
cy.getTestingSelector('view-tab-a734a068-e73d-4b4b-853c-4daffea389c0').click();
|
||||||
|
cy.wait(800);
|
||||||
|
cy.getTestingSelector('view-tab-7f233be4-1b4d-46b2-bcfc-f341b8d75267').click();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,106 @@
|
|||||||
|
import { YDoc, YFolder, YjsEditorKey } from '@/application/collab.type';
|
||||||
|
import { applyYDoc } from '@/application/ydoc/apply';
|
||||||
|
import { FolderProvider } from '@/components/_shared/context-provider/FolderProvider';
|
||||||
|
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
||||||
|
import withAppWrapper from '@/components/app/withAppWrapper';
|
||||||
|
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
import { Database } from 'src/components/database/Database';
|
||||||
|
|
||||||
|
export function renderDatabase(
|
||||||
|
{
|
||||||
|
databaseId,
|
||||||
|
viewId,
|
||||||
|
onNavigateToView,
|
||||||
|
}: {
|
||||||
|
databaseId: string;
|
||||||
|
viewId: string;
|
||||||
|
onNavigateToView: (viewId: string) => void;
|
||||||
|
},
|
||||||
|
onAfterRender?: () => void
|
||||||
|
) {
|
||||||
|
cy.fixture('folder').then((folderJson) => {
|
||||||
|
const doc = new Y.Doc();
|
||||||
|
const state = new Uint8Array(folderJson.data.doc_state);
|
||||||
|
|
||||||
|
applyYDoc(doc, state);
|
||||||
|
|
||||||
|
const folder = doc.getMap(YjsEditorKey.data_section).get(YjsEditorKey.folder) as YFolder;
|
||||||
|
|
||||||
|
cy.fixture(`database/${databaseId}`).then((database) => {
|
||||||
|
cy.fixture(`database/rows/${databaseId}`).then((rows) => {
|
||||||
|
const doc = new Y.Doc();
|
||||||
|
const rootRowsDoc = new Y.Doc();
|
||||||
|
const rowsFolder: Y.Map<YDoc> = rootRowsDoc.getMap();
|
||||||
|
const databaseState = new Uint8Array(database.data.doc_state);
|
||||||
|
|
||||||
|
applyYDoc(doc, databaseState);
|
||||||
|
|
||||||
|
Object.keys(rows).forEach((key) => {
|
||||||
|
const data = rows[key];
|
||||||
|
const rowDoc = new Y.Doc();
|
||||||
|
|
||||||
|
applyYDoc(rowDoc, new Uint8Array(data));
|
||||||
|
rowsFolder.set(key, rowDoc);
|
||||||
|
});
|
||||||
|
|
||||||
|
const AppWrapper = withAppWrapper(() => {
|
||||||
|
return (
|
||||||
|
<div className={'flex h-screen w-screen flex-col py-4'}>
|
||||||
|
<TestDatabase
|
||||||
|
databaseDoc={doc}
|
||||||
|
rows={rowsFolder}
|
||||||
|
folder={folder}
|
||||||
|
iidIndex={viewId}
|
||||||
|
initialViewId={viewId}
|
||||||
|
onNavigateToView={onNavigateToView}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.mount(<AppWrapper />);
|
||||||
|
onAfterRender?.();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TestDatabase({
|
||||||
|
databaseDoc,
|
||||||
|
rows,
|
||||||
|
folder,
|
||||||
|
iidIndex,
|
||||||
|
initialViewId,
|
||||||
|
onNavigateToView,
|
||||||
|
}: {
|
||||||
|
databaseDoc: YDoc;
|
||||||
|
rows: Y.Map<YDoc>;
|
||||||
|
folder: YFolder;
|
||||||
|
iidIndex: string;
|
||||||
|
initialViewId: string;
|
||||||
|
onNavigateToView: (viewId: string) => void;
|
||||||
|
}) {
|
||||||
|
const [activeViewId, setActiveViewId] = useState<string>(initialViewId);
|
||||||
|
|
||||||
|
const handleNavigateToView = (viewId: string) => {
|
||||||
|
setActiveViewId(viewId);
|
||||||
|
onNavigateToView(viewId);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FolderProvider folder={folder}>
|
||||||
|
<IdProvider objectId={iidIndex}>
|
||||||
|
<DatabaseContextProvider
|
||||||
|
viewId={activeViewId || iidIndex}
|
||||||
|
databaseDoc={databaseDoc}
|
||||||
|
rowDocMap={rows}
|
||||||
|
readOnly={true}
|
||||||
|
>
|
||||||
|
<Database iidIndex={iidIndex} viewId={activeViewId} onNavigateToView={handleNavigateToView} />
|
||||||
|
</DatabaseContextProvider>
|
||||||
|
</IdProvider>
|
||||||
|
</FolderProvider>
|
||||||
|
);
|
||||||
|
}
|
@ -17,11 +17,13 @@ export function DatabaseActions() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
conditionsContext?.toggleExpanded();
|
conditionsContext?.toggleExpanded();
|
||||||
}}
|
}}
|
||||||
|
data-testid={'database-actions-filter'}
|
||||||
color={filter.length > 0 ? 'primary' : 'inherit'}
|
color={filter.length > 0 ? 'primary' : 'inherit'}
|
||||||
>
|
>
|
||||||
{t('grid.settings.filter')}
|
{t('grid.settings.filter')}
|
||||||
</TextButton>
|
</TextButton>
|
||||||
<TextButton
|
<TextButton
|
||||||
|
data-testid={'database-actions-sort'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
conditionsContext?.toggleExpanded();
|
conditionsContext?.toggleExpanded();
|
||||||
}}
|
}}
|
||||||
|
@ -16,8 +16,13 @@ export function SelectOptionList({ fieldId, selectedIds }: { fieldId: string; se
|
|||||||
const isSelected = selectedIds.includes(option.id);
|
const isSelected = selectedIds.includes(option.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={option.id} className={'flex items-center justify-between gap-2'}>
|
<div
|
||||||
<Tag label={option.name} color={SelectOptionColorMap[option.color]} />
|
key={option.id}
|
||||||
|
data-testid={'select-option-list'}
|
||||||
|
data-checked={isSelected}
|
||||||
|
className={'flex items-center justify-between gap-2 text-xs'}
|
||||||
|
>
|
||||||
|
<Tag size={'small'} label={option.name} color={SelectOptionColorMap[option.color]} />
|
||||||
{isSelected && <CheckIcon />}
|
{isSelected && <CheckIcon />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -19,6 +19,7 @@ function Filter({ filterId }: { filterId: string }) {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
setAnchorEl(e.currentTarget);
|
setAnchorEl(e.currentTarget);
|
||||||
}}
|
}}
|
||||||
|
data-testid={'database-filter-condition'}
|
||||||
className={
|
className={
|
||||||
'flex cursor-pointer flex-nowrap items-center gap-1 rounded-full border border-line-divider py-1 px-2 hover:border-fill-default hover:text-fill-default hover:shadow-sm'
|
'flex cursor-pointer flex-nowrap items-center gap-1 rounded-full border border-line-divider py-1 px-2 hover:border-fill-default hover:text-fill-default hover:shadow-sm'
|
||||||
}
|
}
|
||||||
@ -39,6 +40,7 @@ function Filter({ filterId }: { filterId: string }) {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
}}
|
}}
|
||||||
|
data-testid={'filter-menu-popover'}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
paper: {
|
paper: {
|
||||||
style: {
|
style: {
|
||||||
|
@ -10,7 +10,10 @@ function FieldMenuTitle({ fieldId, selectedConditionText }: { fieldId: string; s
|
|||||||
</div>
|
</div>
|
||||||
<div className={'flex flex-1 items-center justify-end'}>
|
<div className={'flex flex-1 items-center justify-end'}>
|
||||||
<div className={'flex items-center gap-1'}>
|
<div className={'flex items-center gap-1'}>
|
||||||
<div className={'overflow max-w-[100px] truncate whitespace-nowrap text-xs font-normal'}>
|
<div
|
||||||
|
data-testid={'filter-condition-type'}
|
||||||
|
className={'overflow max-w-[100px] truncate whitespace-nowrap text-xs font-normal'}
|
||||||
|
>
|
||||||
{selectedConditionText}
|
{selectedConditionText}
|
||||||
</div>
|
</div>
|
||||||
<ArrowDownSvg />
|
<ArrowDownSvg />
|
||||||
|
@ -58,6 +58,7 @@ function NumberFilterMenu({ filter }: { filter: NumberFilter }) {
|
|||||||
{displayTextField && (
|
{displayTextField && (
|
||||||
<TextField
|
<TextField
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
data-testid={'number-filter-input'}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
className: 'text-xs p-1.5',
|
className: 'text-xs p-1.5',
|
||||||
|
@ -53,10 +53,11 @@ function TextFilterMenu({ filter }: { filter: TextFilter }) {
|
|||||||
}, [filter.condition]);
|
}, [filter.condition]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex flex-col gap-2 p-2'}>
|
<div className={'flex flex-col gap-2 p-2'} data-testid='text-filter'>
|
||||||
<FieldMenuTitle fieldId={filter.fieldId} selectedConditionText={selectedCondition?.text ?? ''} />
|
<FieldMenuTitle fieldId={filter.fieldId} selectedConditionText={selectedCondition?.text ?? ''} />
|
||||||
{displayTextField && (
|
{displayTextField && (
|
||||||
<TextField
|
<TextField
|
||||||
|
data-testid='text-filter-input'
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
|
@ -8,7 +8,7 @@ function Sort({ sortId }: { sortId: string }) {
|
|||||||
|
|
||||||
if (!sort) return null;
|
if (!sort) return null;
|
||||||
return (
|
return (
|
||||||
<div className={'flex items-center gap-1.5'}>
|
<div data-testid={'sort-condition'} className={'flex items-center gap-1.5'}>
|
||||||
<div className={'w-[120px] max-w-[250px] overflow-hidden rounded-full border border-line-divider py-1 px-2 '}>
|
<div className={'w-[120px] max-w-[250px] overflow-hidden rounded-full border border-line-divider py-1 px-2 '}>
|
||||||
<FieldDisplay fieldId={sort.fieldId} />
|
<FieldDisplay fieldId={sort.fieldId} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,6 +19,7 @@ export function Sorts() {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
setAnchorEl(e.currentTarget);
|
setAnchorEl(e.currentTarget);
|
||||||
}}
|
}}
|
||||||
|
data-testid={'database-sort-condition'}
|
||||||
className='flex cursor-pointer items-center gap-1 rounded-full border border-line-divider px-2 py-1 text-xs hover:border-fill-default hover:text-fill-default hover:shadow-sm'
|
className='flex cursor-pointer items-center gap-1 rounded-full border border-line-divider px-2 py-1 text-xs hover:border-fill-default hover:text-fill-default hover:shadow-sm'
|
||||||
>
|
>
|
||||||
<SortSvg />
|
<SortSvg />
|
||||||
|
@ -73,7 +73,7 @@ export function useLayout() {
|
|||||||
return {
|
return {
|
||||||
viewId: view.get(YjsFolderKey.id),
|
viewId: view.get(YjsFolderKey.id),
|
||||||
name: view.get(YjsFolderKey.name),
|
name: view.get(YjsFolderKey.name),
|
||||||
icon: icon || '',
|
icon: icon || view.get(YjsFolderKey.layout),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.slice(1)
|
.slice(1)
|
||||||
|
@ -1,6 +1,31 @@
|
|||||||
|
import { ViewLayout } from '@/application/collab.type';
|
||||||
import { Crumb, useNavigateToView } from '@/application/folder-yjs';
|
import { Crumb, useNavigateToView } from '@/application/folder-yjs';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ReactComponent as DocumentSvg } from '$icons/16x/document.svg';
|
||||||
|
import { ReactComponent as GridSvg } from '$icons/16x/grid.svg';
|
||||||
|
import { ReactComponent as BoardSvg } from '$icons/16x/board.svg';
|
||||||
|
import { ReactComponent as CalendarSvg } from '$icons/16x/date.svg';
|
||||||
|
|
||||||
|
const renderCrumbIcon = (icon: string) => {
|
||||||
|
if (Number(icon) === ViewLayout.Grid) {
|
||||||
|
return <GridSvg />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(icon) === ViewLayout.Board) {
|
||||||
|
return <BoardSvg />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(icon) === ViewLayout.Calendar) {
|
||||||
|
return <CalendarSvg />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(icon) === ViewLayout.Document) {
|
||||||
|
return <DocumentSvg />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
};
|
||||||
|
|
||||||
function Item({ crumb, disableClick = false }: { crumb: Crumb; disableClick?: boolean }) {
|
function Item({ crumb, disableClick = false }: { crumb: Crumb; disableClick?: boolean }) {
|
||||||
const { viewId, icon, name } = crumb;
|
const { viewId, icon, name } = crumb;
|
||||||
@ -16,7 +41,7 @@ function Item({ crumb, disableClick = false }: { crumb: Crumb; disableClick?: bo
|
|||||||
onNavigateToView?.(viewId);
|
onNavigateToView?.(viewId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon}
|
<span className={'h-4 w-4'}>{renderCrumbIcon(icon)}</span>
|
||||||
<span
|
<span
|
||||||
className={!disableClick ? 'max-w-[250px] truncate hover:text-fill-default hover:underline' : 'flex-1 truncate'}
|
className={!disableClick ? 'max-w-[250px] truncate hover:text-fill-default hover:underline' : 'flex-1 truncate'}
|
||||||
>
|
>
|
||||||
|
@ -78,7 +78,7 @@ export default defineConfig({
|
|||||||
port: !!process.env.TAURI_PLATFORM ? 5173 : process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
port: !!process.env.TAURI_PLATFORM ? 5173 : process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
watch: {
|
watch: {
|
||||||
ignored: ['**/__tests__/**', '**/cypress/**', 'node_modules', '**/*.cy.tsx', '**/*.cy.ts', 'cypress'],
|
ignored: ['node_modules'],
|
||||||
},
|
},
|
||||||
cors: false,
|
cors: false,
|
||||||
},
|
},
|
||||||
@ -143,6 +143,10 @@ export default defineConfig({
|
|||||||
'@mui/icons-material/ErrorOutline',
|
'@mui/icons-material/ErrorOutline',
|
||||||
'@mui/icons-material/CheckCircleOutline',
|
'@mui/icons-material/CheckCircleOutline',
|
||||||
'@mui/icons-material/FunctionsOutlined',
|
'@mui/icons-material/FunctionsOutlined',
|
||||||
|
'react-katex',
|
||||||
|
// 'react-custom-scrollbars-2',
|
||||||
|
// 'react-window',
|
||||||
|
// 'react-virtualized-auto-sizer',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user