Skip to content

Commit

Permalink
fix(dashboard): fix edit mode bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
jmbuss committed Mar 7, 2023
1 parent 488a61d commit 2e88abf
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnyWidget } from '..';
import { AnyWidget } from '../types';

/**
* map of widget type to a generator func to create properties when this widget is dropped into the grid
Expand Down
147 changes: 147 additions & 0 deletions packages/dashboard/src/customization/widgets/text/component.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React from 'react';

import { render, RenderResult } from '@testing-library/react';

import { act } from 'react-dom/test-utils';

import { Provider, useSelector } from 'react-redux';
import UserEvent from '@testing-library/user-event';

import TextWidgetComponent from './component';
import { TextWidget } from '../types';
import { MOCK_TEXT_WIDGET } from '../../../../testing/mocks';

import { configureDashboardStore } from '../../../store';
import { DashboardState } from '../../../store/state';

type RenderTextWidgetArgs = {
textWidget?: TextWidget;
readOnlyMode?: boolean;
isSelected?: boolean;
remove?: boolean;
};

const WidgetWrapper: React.FC<{ widget: TextWidget; remove: boolean }> = ({ widget, remove }) => {
if (remove) return null;

const widgets = useSelector((state: DashboardState) => state.dashboardConfiguration.widgets);
const textWidget = widgets.find((w) => w.id === widget.id);

return <TextWidgetComponent {...textWidget} />;
};

const renderWrapper = (renderFunc: (ui: React.ReactElement) => RenderResult | void, args?: RenderTextWidgetArgs) => {
const widget = args?.textWidget || MOCK_TEXT_WIDGET;
const readOnly = args?.readOnlyMode || false;
const selected = args?.isSelected || false;
const shouldRemove = args?.remove || false;
const store = configureDashboardStore({
readOnly,
dashboardConfiguration: {
widgets: [widget],
},
selectedWidgets: selected ? [widget] : [],
});

const renderResult = renderFunc(
<Provider store={store}>
<WidgetWrapper widget={widget} remove={shouldRemove} />
</Provider>
);

return { store, renderResult };
};

const renderTextWidget = (args?: RenderTextWidgetArgs) => {
const { renderResult, store } = renderWrapper(render, args);

if (!renderResult) throw new Error('Could not properly setup test renderer');

const { container, rerender } = renderResult;

const rerenderTextWidget = (rerenderArgs?: RenderTextWidgetArgs) => {
renderWrapper(rerender, rerenderArgs);
};

return { container, rerenderTextWidget, store };
};

describe('Text Widget', () => {
it('is editable when clicked while selected', async () => {
const { container, store } = renderTextWidget({ isSelected: true });

const textWidgetDisplay = container.querySelector('p.text-widget');
expect(textWidgetDisplay).toBeInTheDocument();

if (!textWidgetDisplay) throw new Error('text widget not mounted');

expect(store.getState().grid.enabled).toEqual(true);

await act(async () => {
await UserEvent.pointer({
keys: '[MouseLeft][/MouseLeft]',
target: textWidgetDisplay,
});
});

expect(store.getState().grid.enabled).toEqual(false);
const textWidgetTextArea = container.querySelector('textarea.text-widget');
expect(textWidgetTextArea).toBeInTheDocument();

await act(async () => {
await UserEvent.keyboard('-editable');
});

expect(store.getState().dashboardConfiguration.widgets).toEqual(
expect.arrayContaining([
expect.objectContaining({
properties: {
value: MOCK_TEXT_WIDGET.properties.value + '-editable',
},
}),
])
);
});

it('is not editable when in read only', async () => {
const { container } = renderTextWidget({ readOnlyMode: true });

const textWidgetDisplay = container.querySelector('p.text-widget');
expect(textWidgetDisplay).toBeInTheDocument();

if (!textWidgetDisplay) throw new Error('text widget not mounted');

await act(async () => {
await UserEvent.pointer({
keys: '[MouseLeft][/MouseLeft]',
target: textWidgetDisplay,
});
});

const textWidgetTextArea = container.querySelector('textarea.text-widget');
expect(textWidgetTextArea).not.toBeInTheDocument();
});

it('dashboard grid becomes editable when the widget is removed', async () => {
const { container, rerenderTextWidget, store } = renderTextWidget({ isSelected: true });

const textWidgetDisplay = container.querySelector('p.text-widget');

if (!textWidgetDisplay) throw new Error('text widget not mounted');

expect(store.getState().grid.enabled).toEqual(true);

await act(async () => {
await UserEvent.pointer({
keys: '[MouseLeft][/MouseLeft]',
target: textWidgetDisplay,
});
});

expect(store.getState().grid.enabled).toEqual(false);

rerenderTextWidget({ remove: true });

expect(store.getState().grid.enabled).toEqual(true);
});
});
12 changes: 11 additions & 1 deletion packages/dashboard/src/customization/widgets/text/component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { onChangeDashboardGridEnabledAction } from '~/store/actions';

Expand Down Expand Up @@ -28,6 +28,16 @@ const TextWidgetComponent: React.FC<TextWidget> = (widget) => {
setIsEditing(editing);
};

useEffect(() => {
return () => {
/**
* Handle edge case where a user right click deletes
* the widget while in edit mode
*/
handleSetEdit(false);
};
}, []);

const props = { readOnly, isSelected, handleSetEdit, ...widget };

if (readOnly) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
import React, { PointerEventHandler, useEffect, useState } from 'react';

import StyledText from './index';
import { TextWidget } from '../../types';
import { useIsSelected } from '~/customization/hooks/useIsSelected';
import { MouseClick } from '~/types';

type EditableStyledTextProps = TextWidget & {
handleSetEdit: (isEditing: boolean) => void;
Expand All @@ -18,10 +19,12 @@ const EditableStyledText: React.FC<EditableStyledTextProps> = ({ handleSetEdit,
setEditStaged(false);
}, [x, y]);

const handleStageEdit = () => {
const handleStageEdit: PointerEventHandler = (e) => {
if (e.button !== MouseClick.Left) return;
setEditStaged(true);
};
const handleToggleEdit = () => {
const handleToggleEdit: PointerEventHandler = (e) => {
if (e.button !== MouseClick.Left) return;
if (isSelected && editStaged) {
handleSetEdit(true);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { CSSProperties } from 'react';
import React, { CSSProperties, PointerEventHandler } from 'react';
import { TextWidget } from '../../types';
import { defaultFontSettings } from './defaultFontSettings';

import './index.css';

type StyledTextProps = TextWidget & {
onPointerDown?: () => void;
onPointerUp?: () => void;
onPointerDown?: PointerEventHandler;
onPointerUp?: PointerEventHandler;
};

const StyledText: React.FC<StyledTextProps> = ({ onPointerDown, onPointerUp, ...widget }) => {
Expand Down
6 changes: 3 additions & 3 deletions packages/dashboard/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Dashboard from './components/dashboard';
export * from './types';
import Dashboard, { DashboardProps } from './components/dashboard';
import { DashboardConfiguration, Widget } from './types';

export { Dashboard };
export { Dashboard, DashboardProps, DashboardConfiguration, Widget };

0 comments on commit 2e88abf

Please sign in to comment.