Skip to content
This repository has been archived by the owner on Nov 28, 2024. It is now read-only.

Commit

Permalink
Merge pull request #14 from graasp/13/ws-children-updates
Browse files Browse the repository at this point in the history
#13 feat: add real-time updates for children recursively
  • Loading branch information
Alexandre Chau authored Jul 15, 2021
2 parents 0474da5 + a34824b commit c7bda66
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 16 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@

```
REACT_APP_API_HOST=http://localhost:3000
PORT=3111
PORT=3112
REACT_APP_SHOW_NOTIFICATIONS=true
REACT_APP_AUTHENTICATION_HOST=http://localhost:3001
```

4. Run `yarn start`. The client should be accessible at `localhost:3111`
4. Run `yarn start`. The client should be accessible at `localhost:3112`

## Testing

Set the following environnement variables in `.env.test`

```
REACT_APP_API_HOST=http://localhost:3000
PORT=3111
PORT=3112
REACT_APP_SHOW_NOTIFICATIONS=false
REACT_APP_NODE_ENV=test
```
Expand Down
2 changes: 1 addition & 1 deletion cypress.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"baseUrl": "http://localhost:3000",
"baseUrl": "http://localhost:3112",
"video": false,
"retries": {
"runMode": 2
Expand Down
90 changes: 90 additions & 0 deletions cypress/integration/ws.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { WebSocket } from '@graasp/websockets/test/mock-client';
import { buildMainPath } from '../../src/config/paths';
import {
buildFolderButtonId,
FOLDER_NAME_TITLE_CLASS,
} from '../../src/config/selectors';
import { FOLDER_WITH_SUBFOLDER_ITEM } from '../fixtures/items';
import { CURRENT_USER } from '../fixtures/members';
import { expectFolderButtonLayout } from '../support/integrationUtils';
import { mockGetChildren, mockGetItem } from '../support/server';

function beforeWs(visitRoute, wsClientStub) {
cy.visit(visitRoute, {
onBeforeLoad: (win) => {
cy.stub(win, 'WebSocket', () => wsClientStub);
},
});
}

describe('Websocket interactions', () => {
let client;
const { items } = FOLDER_WITH_SUBFOLDER_ITEM;
const newChild = {
...FOLDER_WITH_SUBFOLDER_ITEM.items[1],
id: 'deadbeef-aaaa-bbbb-cccc-0242ac130002',
name: 'newChild',
};

beforeEach(() => {
client = new WebSocket();
mockGetItem({ items, currentMember: CURRENT_USER }, false);
mockGetChildren(items);
});

it('Displays create child update', () => {
const parent = FOLDER_WITH_SUBFOLDER_ITEM.items[0];
beforeWs(buildMainPath({ rootId: parent.id, id: null }), client);

cy.get(`.${FOLDER_NAME_TITLE_CLASS}`)
.should('contain', parent.name)
.then(() => {
expectFolderButtonLayout(FOLDER_WITH_SUBFOLDER_ITEM.items[1]);

// also add to mock data to avoid error while refetching
items.push(newChild);
// receive create update for subfolder
client.receive({
realm: 'notif',
type: 'update',
channel: parent.id,
body: {
entity: 'item',
kind: 'childItem',
op: 'create',
value: newChild,
},
});

expectFolderButtonLayout(newChild);
});
});

it('Displays remove child update', () => {
const parent = FOLDER_WITH_SUBFOLDER_ITEM.items[0];
beforeWs(buildMainPath({ rootId: parent.id, id: null }), client);

// button should exist
cy.get(`#${buildFolderButtonId(newChild.id)}`)
.should('exist')
.then(() => {
expectFolderButtonLayout(FOLDER_WITH_SUBFOLDER_ITEM.items[1]);

// receive remove update for subfolder
client.receive({
realm: 'notif',
type: 'update',
channel: parent.id,
body: {
entity: 'item',
kind: 'childItem',
op: 'delete',
value: newChild,
},
});

// button should be removed
cy.get(`#${buildFolderButtonId(newChild.id)}`).should('not.exist');
});
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
]
},
"devDependencies": {
"@graasp/websockets": "git://github.com/graasp/graasp-websockets.git",
"@commitlint/cli": "12.1.1",
"@commitlint/config-conventional": "12.1.1",
"@cypress/code-coverage": "3.9.6",
Expand Down
6 changes: 4 additions & 2 deletions src/components/common/Item.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import Alert from '@material-ui/lab/Alert';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { hooks } from '../../config/queryClient';
import { hooks, ws } from '../../config/queryClient';
import { ITEM_TYPES } from '../../enums';
import FolderButton from './FolderButton';
import {
Expand All @@ -38,9 +38,11 @@ const Item = ({ id, isChildren }) => {
const { data: item, isLoading, isError } = useItem(id);

// fetch children if item is folder
const isFolder = Boolean(item?.get('type') === ITEM_TYPES.FOLDER);
const { data: children, isLoading: isChildrenLoading } = useChildren(id, {
enabled: Boolean(item?.get('type') === ITEM_TYPES.FOLDER),
enabled: isFolder,
});
ws.hooks.useChildrenUpdates(isFolder ? id : null);

// fetch file content if type is file
const { data: content, isError: isFileError } = useFileContent(id, {
Expand Down
17 changes: 14 additions & 3 deletions src/components/common/MainMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Loader,
} from '@graasp/ui';
import { buildTreeItemClass, MAIN_MENU_ID } from '../../config/selectors';
import { hooks } from '../../config/queryClient';
import { hooks, ws } from '../../config/queryClient';
import { ITEM_TYPES } from '../../enums';

const { useItem, useChildren } = hooks;
Expand All @@ -23,13 +23,18 @@ const MainMenu = () => {
isLoading: rootItemIsLoading,
isError: rootItemIsError,
} = useItem(rootId);

const isFolder = Boolean(
rootItem && rootItem.get('type') === ITEM_TYPES.FOLDER,
);
const {
data: children,
isLoading,
isError: childrenIsError,
} = useChildren(rootId, {
enabled: Boolean(rootItem && rootItem.get('type') === ITEM_TYPES.FOLDER),
enabled: isFolder,
});
ws.hooks.useChildrenUpdates(isFolder ? rootId : null);

// display nothing when no item is defined
if (!rootId) {
Expand All @@ -44,13 +49,19 @@ const MainMenu = () => {
return <Alert severity="error">{t('An unexpected error occured.')}</Alert>;
}

const useChildrenWithUpdates = (childId, ...args) => {
const ret = useChildren(childId, ...args);
ws.hooks.useChildrenUpdates(childId);
return ret;
};

return (
<GraaspMainMenu id={MAIN_MENU_ID}>
<DynamicTreeView
rootLabel={rootItem.get('name')}
rootId={rootId}
useItem={useItem}
useChildren={useChildren}
useChildren={useChildrenWithUpdates}
buildTreeItemClass={(nodeId) => buildTreeItemClass(nodeId)}
initialExpendedItems={[rootId]}
showCheckbox={false}
Expand Down
3 changes: 3 additions & 0 deletions src/config/queryClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ const {
queryClient,
QueryClientProvider,
hooks,
ws,
useMutation,
ReactQueryDevtools,
} = configureQueryClient({
API_HOST,
notifier,
enableWebsocket: true,
});
export {
queryClient,
QueryClientProvider,
hooks,
ws,
useMutation,
ReactQueryDevtools,
};
Loading

0 comments on commit c7bda66

Please sign in to comment.