Skip to content

Commit

Permalink
refactor: remove openDialogCount from DialogManager
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela committed Sep 11, 2024
1 parent 401af81 commit 4f120a4
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 124 deletions.
34 changes: 17 additions & 17 deletions src/components/Dialog/DialogManager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { StateStore } from 'stream-chat';

type DialogId = string;

export type GetOrCreateParams = {
export type GetOrCreateDialogParams = {
id: DialogId;
};

type DialogId = string;

export type Dialog = {
close: () => void;
id: DialogId;
Expand All @@ -16,29 +16,34 @@ export type Dialog = {
toggleSingle: () => void;
};

type DialogInitOptions = {
export type DialogManagerOptions = {
id?: string;
};

type Dialogs = Record<DialogId, Dialog>;

type DialogManagerState = {
export type DialogManagerState = {
dialogsById: Dialogs;
openDialogCount: number;
};

export class DialogManager {
id: string;
state = new StateStore<DialogManagerState>({
dialogsById: {},
openDialogCount: 0,
});

constructor({ id }: DialogInitOptions = {}) {
constructor({ id }: DialogManagerOptions = {}) {
this.id = id ?? new Date().getTime().toString();
}

getOrCreate({ id }: GetOrCreateParams) {
get openDialogCount() {
return Object.values(this.state.getLatestValue().dialogsById).reduce((count, dialog) => {
if (dialog.isOpen) return count + 1;
return count;
}, 0);
}

getOrCreate({ id }: GetOrCreateDialogParams) {
let dialog = this.state.getLatestValue().dialogsById[id];
if (!dialog) {
dialog = {
Expand Down Expand Up @@ -68,7 +73,7 @@ export class DialogManager {
return dialog;
}

open(params: GetOrCreateParams, closeRest?: boolean) {
open(params: GetOrCreateDialogParams, closeRest?: boolean) {
const dialog = this.getOrCreate(params);
if (dialog.isOpen) return;
if (closeRest) {
Expand All @@ -77,7 +82,6 @@ export class DialogManager {
this.state.next((current) => ({
...current,
dialogsById: { ...current.dialogsById, [dialog.id]: { ...dialog, isOpen: true } },
openDialogCount: ++current.openDialogCount,
}));
}

Expand All @@ -87,23 +91,22 @@ export class DialogManager {
this.state.next((current) => ({
...current,
dialogsById: { ...current.dialogsById, [dialog.id]: { ...dialog, isOpen: false } },
openDialogCount: --current.openDialogCount,
}));
}

closeAll() {
Object.values(this.state.getLatestValue().dialogsById).forEach((dialog) => dialog.close());
}

toggleOpen(params: GetOrCreateParams) {
toggleOpen(params: GetOrCreateDialogParams) {
if (this.state.getLatestValue().dialogsById[params.id]?.isOpen) {
this.close(params.id);
} else {
this.open(params);
}
}

toggleOpenSingle(params: GetOrCreateParams) {
toggleOpenSingle(params: GetOrCreateDialogParams) {
if (this.state.getLatestValue().dialogsById[params.id]?.isOpen) {
this.close(params.id);
} else {
Expand All @@ -122,9 +125,6 @@ export class DialogManager {
return {
...current,
dialogsById: newDialogs,
openDialogCount:
current.openDialogCount &&
(dialog.isOpen ? current.openDialogCount - 1 : current.openDialogCount),
};
});
}
Expand Down
21 changes: 4 additions & 17 deletions src/components/Dialog/DialogPortal.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import React, { PropsWithChildren, useEffect, useLayoutEffect, useState } from 'react';
import React, { PropsWithChildren, useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { useDialogIsOpen } from './hooks';
import { useDialogIsOpen, useOpenedDialogCount } from './hooks';
import { useDialogManager } from '../../context';

export const DialogPortalDestination = () => {
const { dialogManager } = useDialogManager();
const [shouldRender, setShouldRender] = useState(
!!dialogManager.state.getLatestValue().openDialogCount,
);

useEffect(
() =>
dialogManager.state.subscribeWithSelector<number[]>(
({ openDialogCount }) => [openDialogCount],
([openDialogCount]) => {
setShouldRender(openDialogCount > 0);
},
),
[dialogManager],
);
const openedDialogCount = useOpenedDialogCount();

return (
<div
Expand All @@ -28,7 +15,7 @@ export const DialogPortalDestination = () => {
onClick={() => dialogManager.closeAll()}
style={
{
'--str-chat__dialog-overlay-height': shouldRender ? '100%' : '0',
'--str-chat__dialog-overlay-height': openedDialogCount > 0 ? '100%' : '0',
} as React.CSSProperties
}
></div>
Expand Down
144 changes: 72 additions & 72 deletions src/components/Dialog/__tests__/DialogsManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ const dialogId = 'dialogId';
describe('DialogManager', () => {
it('initiates with provided options', () => {
const id = 'XX';
const dm = new DialogManager({ id });
expect(dm.id).toBe(id);
const dialogManager = new DialogManager({ id });
expect(dialogManager.id).toBe(id);
});
it('initiates with default options', () => {
const mockedId = '12345';
const spy = jest.spyOn(Date.prototype, 'getTime').mockReturnValueOnce(mockedId);
const dm = new DialogManager();
expect(dm.id).toBe(mockedId);
const dialogManager = new DialogManager();
expect(dialogManager.id).toBe(mockedId);
spy.mockRestore();
});
it('creates a new closed dialog', () => {
const dm = new DialogManager();
expect(Object.keys(dm.state.getLatestValue().dialogsById)).toHaveLength(0);
expect(dm.getOrCreate({ id: dialogId })).toMatchObject({
const dialogManager = new DialogManager();
expect(Object.keys(dialogManager.state.getLatestValue().dialogsById)).toHaveLength(0);
expect(dialogManager.getOrCreate({ id: dialogId })).toMatchObject({
close: expect.any(Function),
id: 'dialogId',
isOpen: false,
Expand All @@ -27,27 +27,27 @@ describe('DialogManager', () => {
toggle: expect.any(Function),
toggleSingle: expect.any(Function),
});
expect(Object.keys(dm.state.getLatestValue().dialogsById)).toHaveLength(1);
expect(dm.state.getLatestValue().openDialogCount).toBe(0);
expect(Object.keys(dialogManager.state.getLatestValue().dialogsById)).toHaveLength(1);
expect(dialogManager.openDialogCount).toBe(0);
});

it('retrieves an existing dialog', () => {
const dm = new DialogManager();
dm.state.next((current) => ({
const dialogManager = new DialogManager();
dialogManager.state.next((current) => ({
...current,
dialogsById: { ...current.dialogsById, [dialogId]: { id: dialogId, isOpen: true } },
}));
expect(dm.getOrCreate({ id: dialogId })).toMatchObject({
expect(dialogManager.getOrCreate({ id: dialogId })).toMatchObject({
id: 'dialogId',
isOpen: true,
});
expect(Object.keys(dm.state.getLatestValue().dialogsById)).toHaveLength(1);
expect(Object.keys(dialogManager.state.getLatestValue().dialogsById)).toHaveLength(1);
});

it('creates a dialog if it does not exist on open', () => {
const dm = new DialogManager();
dm.open({ id: dialogId });
expect(dm.state.getLatestValue().dialogsById[dialogId]).toMatchObject({
const dialogManager = new DialogManager();
dialogManager.open({ id: dialogId });
expect(dialogManager.state.getLatestValue().dialogsById[dialogId]).toMatchObject({
close: expect.any(Function),
id: 'dialogId',
isOpen: true,
Expand All @@ -56,92 +56,92 @@ describe('DialogManager', () => {
toggle: expect.any(Function),
toggleSingle: expect.any(Function),
});
expect(dm.state.getLatestValue().openDialogCount).toBe(1);
expect(dialogManager.openDialogCount).toBe(1);
});

it('opens existing dialog', () => {
const dm = new DialogManager();
dm.getOrCreate({ id: dialogId });
dm.open({ id: dialogId });
expect(dm.state.getLatestValue().dialogsById[dialogId].isOpen).toBeTruthy();
expect(dm.state.getLatestValue().openDialogCount).toBe(1);
const dialogManager = new DialogManager();
dialogManager.getOrCreate({ id: dialogId });
dialogManager.open({ id: dialogId });
expect(dialogManager.state.getLatestValue().dialogsById[dialogId].isOpen).toBeTruthy();
expect(dialogManager.openDialogCount).toBe(1);
});

it('does not open already open dialog', () => {
const dm = new DialogManager();
dm.getOrCreate({ id: dialogId });
dm.open({ id: dialogId });
dm.open({ id: dialogId });
expect(dm.state.getLatestValue().openDialogCount).toBe(1);
const dialogManager = new DialogManager();
dialogManager.getOrCreate({ id: dialogId });
dialogManager.open({ id: dialogId });
dialogManager.open({ id: dialogId });
expect(dialogManager.openDialogCount).toBe(1);
});

it('closes all other dialogsById before opening the target', () => {
const dm = new DialogManager();
dm.open({ id: 'xxx' });
dm.open({ id: 'yyy' });
expect(dm.state.getLatestValue().openDialogCount).toBe(2);
dm.open({ id: dialogId }, true);
const dialogs = dm.state.getLatestValue().dialogsById;
const dialogManager = new DialogManager();
dialogManager.open({ id: 'xxx' });
dialogManager.open({ id: 'yyy' });
expect(dialogManager.openDialogCount).toBe(2);
dialogManager.open({ id: dialogId }, true);
const dialogs = dialogManager.state.getLatestValue().dialogsById;
expect(dialogs.xxx.isOpen).toBeFalsy();
expect(dialogs.yyy.isOpen).toBeFalsy();
expect(dm.state.getLatestValue().dialogsById[dialogId].isOpen).toBeTruthy();
expect(dm.state.getLatestValue().openDialogCount).toBe(1);
expect(dialogManager.state.getLatestValue().dialogsById[dialogId].isOpen).toBeTruthy();
expect(dialogManager.openDialogCount).toBe(1);
});

it('closes opened dialog', () => {
const dm = new DialogManager();
dm.open({ id: dialogId });
dm.close(dialogId);
expect(dm.state.getLatestValue().dialogsById[dialogId].isOpen).toBeFalsy();
expect(dm.state.getLatestValue().openDialogCount).toBe(0);
const dialogManager = new DialogManager();
dialogManager.open({ id: dialogId });
dialogManager.close(dialogId);
expect(dialogManager.state.getLatestValue().dialogsById[dialogId].isOpen).toBeFalsy();
expect(dialogManager.openDialogCount).toBe(0);
});

it('does not close already closed dialog', () => {
const dm = new DialogManager();
dm.open({ id: 'xxx' });
dm.open({ id: dialogId });
dm.close(dialogId);
dm.close(dialogId);
expect(dm.state.getLatestValue().openDialogCount).toBe(1);
const dialogManager = new DialogManager();
dialogManager.open({ id: 'xxx' });
dialogManager.open({ id: dialogId });
dialogManager.close(dialogId);
dialogManager.close(dialogId);
expect(dialogManager.openDialogCount).toBe(1);
});

it('toggles the open state of a dialog', () => {
const dm = new DialogManager();
dm.open({ id: 'xxx' });
dm.open({ id: 'yyy' });
dm.toggleOpen({ id: dialogId });
expect(dm.state.getLatestValue().openDialogCount).toBe(3);
dm.toggleOpen({ id: dialogId });
expect(dm.state.getLatestValue().openDialogCount).toBe(2);
const dialogManager = new DialogManager();
dialogManager.open({ id: 'xxx' });
dialogManager.open({ id: 'yyy' });
dialogManager.toggleOpen({ id: dialogId });
expect(dialogManager.openDialogCount).toBe(3);
dialogManager.toggleOpen({ id: dialogId });
expect(dialogManager.openDialogCount).toBe(2);
});

it('keeps single opened dialog when the toggling open dialog state', () => {
const dm = new DialogManager();
const dialogManager = new DialogManager();

dm.open({ id: 'xxx' });
dm.open({ id: 'yyy' });
dm.toggleOpenSingle({ id: dialogId });
expect(dm.state.getLatestValue().openDialogCount).toBe(1);
dialogManager.open({ id: 'xxx' });
dialogManager.open({ id: 'yyy' });
dialogManager.toggleOpenSingle({ id: dialogId });
expect(dialogManager.openDialogCount).toBe(1);

dm.toggleOpenSingle({ id: dialogId });
expect(dm.state.getLatestValue().openDialogCount).toBe(0);
dialogManager.toggleOpenSingle({ id: dialogId });
expect(dialogManager.openDialogCount).toBe(0);
});

it('removes a dialog', () => {
const dm = new DialogManager();
dm.getOrCreate({ id: dialogId });
dm.open({ id: dialogId });
dm.remove(dialogId);
expect(dm.state.getLatestValue().openDialogCount).toBe(0);
expect(Object.keys(dm.state.getLatestValue().dialogsById)).toHaveLength(0);
const dialogManager = new DialogManager();
dialogManager.getOrCreate({ id: dialogId });
dialogManager.open({ id: dialogId });
dialogManager.remove(dialogId);
expect(dialogManager.openDialogCount).toBe(0);
expect(Object.keys(dialogManager.state.getLatestValue().dialogsById)).toHaveLength(0);
});

it('handles attempt to remove non-existent dialog', () => {
const dm = new DialogManager();
dm.getOrCreate({ id: dialogId });
dm.open({ id: dialogId });
dm.remove('xxx');
expect(dm.state.getLatestValue().openDialogCount).toBe(1);
expect(Object.keys(dm.state.getLatestValue().dialogsById)).toHaveLength(1);
const dialogManager = new DialogManager();
dialogManager.getOrCreate({ id: dialogId });
dialogManager.open({ id: dialogId });
dialogManager.remove('xxx');
expect(dialogManager.openDialogCount).toBe(1);
expect(Object.keys(dialogManager.state.getLatestValue().dialogsById)).toHaveLength(1);
});
});
Loading

0 comments on commit 4f120a4

Please sign in to comment.