Skip to content

Commit

Permalink
fix: chat button should appear above content tools (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
alangsto authored Aug 31, 2023
1 parent 438c5c3 commit 71ff0e2
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 20 deletions.
21 changes: 17 additions & 4 deletions src/components/ToggleXpertButton/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { Close } from '@edx/paragon/icons';
import { ReactComponent as XpertLogo } from '../../assets/xpert-logo.svg';
import './index.scss';

const ToggleXpert = ({ isOpen, setIsOpen, courseId }) => {
const ToggleXpert = ({
isOpen,
setIsOpen,
courseId,
contentToolsEnabled,
}) => {
const [hasDismissed, setHasDismissed] = useState(false);
const handleClick = () => {
// log event if the tool is opened
Expand All @@ -29,9 +34,16 @@ const ToggleXpert = ({ isOpen, setIsOpen, courseId }) => {

return (
(!isOpen && (
<div className="toggle closed d-flex flex-column position-fixed justify-content-end align-items-end mx-3 border-0">
<div className={
`toggle closed d-flex flex-column position-fixed justify-content-end align-items-end mx-3 border-0
${contentToolsEnabled ? 'chat-content-tools-margin' : ''}`
}
>
{!hasDismissed && (
<div className="d-flex justify-content-end flex-row" data-testid="action-message">
<div
className="d-flex justify-content-end flex-row"
data-testid="action-message"
>
<IconButton
src={Close}
iconAs={Icon}
Expand All @@ -41,7 +53,7 @@ const ToggleXpert = ({ isOpen, setIsOpen, courseId }) => {
className="dismiss-button mx-2 mt-1 bg-gray"
size="sm"
/>
<div className="action-message open-negative-margin p-3 mb-5.5">
<div className="action-message open-negative-margin p-3 mb-4.5">
<span>
Hi there! 👋 I&apos;m Xpert,
an AI-powered assistant from edX who can help you with questions about this course.
Expand All @@ -66,6 +78,7 @@ ToggleXpert.propTypes = {
isOpen: PropTypes.bool.isRequired,
setIsOpen: PropTypes.func.isRequired,
courseId: PropTypes.string.isRequired,
contentToolsEnabled: PropTypes.bool.isRequired,
};

export default ToggleXpert;
7 changes: 6 additions & 1 deletion src/components/ToggleXpertButton/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

&.button-icon {
background-color: variables.$dark-green;
position: inherit;
position: relative;
}

&.open {
Expand Down Expand Up @@ -40,3 +40,8 @@
width: 1.5rem !important;
height: 1.5rem !important;
}

// this class is used to shift the display of the toggle to account for the display of content tools
.chat-content-tools-margin {
margin-bottom: 2rem;
}
5 changes: 5 additions & 0 deletions src/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const learningAssistantSlice = createSlice({
apiIsLoading: false,
conversationId: uuidv4(),
disclosureAcknowledged: false,
sidebarIsOpen: false,
},
reducers: {
setCurrentMessage: (state, { payload }) => {
Expand Down Expand Up @@ -39,6 +40,9 @@ export const learningAssistantSlice = createSlice({
setDisclosureAcknowledged: (state, { payload }) => {
state.disclosureAcknowledged = payload;
},
setSidebarIsOpen: (state, { payload }) => {
state.sidebarIsOpen = payload;
},
},
});

Expand All @@ -51,6 +55,7 @@ export const {
setApiIsLoading,
resetApiError,
setDisclosureAcknowledged,
setSidebarIsOpen,
} = learningAssistantSlice.actions;

export const {
Expand Down
7 changes: 7 additions & 0 deletions src/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
setApiIsLoading,
resetApiError,
setDisclosureAcknowledged,
setSidebarIsOpen,
} from './slice';

export function addChatMessage(role, content, courseId) {
Expand Down Expand Up @@ -82,3 +83,9 @@ export function acknowledgeDisclosure(isDisclosureAcknowledged) {
dispatch(setDisclosureAcknowledged(isDisclosureAcknowledged));
};
}

export function updateSidebarIsOpen(isOpen) {
return (dispatch) => {
dispatch(setSidebarIsOpen(isOpen));
};
}
17 changes: 14 additions & 3 deletions src/widgets/Xpert.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import PropTypes from 'prop-types';
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { updateSidebarIsOpen } from '../data/thunks';
import ToggleXpert from '../components/ToggleXpertButton';
import Sidebar from '../components/Sidebar';

const Xpert = ({ courseId }) => {
const [sidebarIsOpen, setSidebarIsOpen] = useState(false);
const Xpert = ({ courseId, contentToolsEnabled }) => {
const dispatch = useDispatch();

const {
sidebarIsOpen,
} = useSelector(state => state.learningAssistant);

const setSidebarIsOpen = (isOpen) => {
dispatch(updateSidebarIsOpen(isOpen));
};

return (
<div>
<ToggleXpert
courseId={courseId}
isOpen={sidebarIsOpen}
setIsOpen={setSidebarIsOpen}
contentToolsEnabled={contentToolsEnabled}
/>
<Sidebar
courseId={courseId}
Expand All @@ -24,6 +34,7 @@ const Xpert = ({ courseId }) => {

Xpert.propTypes = {
courseId: PropTypes.string.isRequired,
contentToolsEnabled: PropTypes.bool.isRequired,
};

export default Xpert;
28 changes: 16 additions & 12 deletions src/widgets/Xpert.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const initialState = {
// TEMPORARY: This is simply to ensure that the tests pass by hiding the disclosure.
// I will remove this and write tests in a future pull request.
disclosureAcknowledged: true,
sidebarIsOpen: false,
},
};
const courseId = 'course-v1:edX+DemoX+Demo_Course';
Expand All @@ -41,7 +42,7 @@ beforeEach(() => {
});

test('initial load displays correct elements', () => {
render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

// button to open chat should be in the DOM
expect(screen.queryByTestId('toggle-button')).toBeVisible();
Expand All @@ -52,7 +53,7 @@ test('initial load displays correct elements', () => {
});
test('clicking the call to action dismiss button removes the message', async () => {
const user = userEvent.setup();
render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

// button to open chat should be in the DOM
expect(screen.queryByTestId('toggle-button')).toBeVisible();
Expand All @@ -65,7 +66,7 @@ test('clicking the call to action dismiss button removes the message', async ()
test('clicking the toggle button opens the sidebar', async () => {
const user = userEvent.setup();

render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

await user.click(screen.queryByTestId('toggle-button'));

Expand All @@ -83,7 +84,7 @@ test('submitted text appears as message in the sidebar', async () => {
const user = userEvent.setup();
const userMessage = 'Hello, Xpert!';

render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

await user.click(screen.queryByTestId('toggle-button'));

Expand Down Expand Up @@ -111,7 +112,7 @@ test('loading message appears in the sidebar while the response loads', async ()
const responseMessage = createRandomResponseForTesting();
jest.spyOn(api, 'default').mockResolvedValue(responseMessage);

render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

await user.click(screen.queryByTestId('toggle-button'));

Expand All @@ -135,7 +136,7 @@ test('response text appears as message in the sidebar', async () => {
const responseMessage = createRandomResponseForTesting();
jest.spyOn(api, 'default').mockResolvedValue(responseMessage);

render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

await user.click(screen.queryByTestId('toggle-button'));

Expand All @@ -156,7 +157,7 @@ test('clicking the clear button clears messages in the sidebar', async () => {
const responseMessage = createRandomResponseForTesting();
jest.spyOn(api, 'default').mockImplementation(() => responseMessage);

render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

await user.click(screen.queryByTestId('toggle-button'));

Expand All @@ -174,7 +175,7 @@ test('clicking the clear button clears messages in the sidebar', async () => {
});
test('clicking the close button closes the sidebar', async () => {
const user = userEvent.setup();
render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

await user.click(screen.queryByTestId('toggle-button'));
await user.click(screen.getByTestId('close-button'));
Expand All @@ -184,7 +185,7 @@ test('clicking the close button closes the sidebar', async () => {
});
test('toggle elements do not appear when sidebar is open', async () => {
const user = userEvent.setup();
render(<Xpert courseId={courseId} />, { preloadedState: initialState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: initialState });

await user.click(screen.queryByTestId('toggle-button'));

Expand All @@ -204,9 +205,10 @@ test('error message should disappear upon succesful api call', async () => {
// TEMPORARY: This is simply to ensure that the tests pass by hiding the disclosure.
// I will remove this and write tests in a future pull request.
disclosureAcknowledged: true,
sidebarIsOpen: false,
},
};
render(<Xpert courseId={courseId} />, { preloadedState: errorState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: errorState });

await user.click(screen.queryByTestId('toggle-button'));

Expand Down Expand Up @@ -234,9 +236,10 @@ test('error message should disappear when dismissed', async () => {
// TEMPORARY: This is simply to ensure that the tests pass by hiding the disclosure.
// I will remove this and write tests in a future pull request.
disclosureAcknowledged: true,
sidebarIsOpen: false,
},
};
render(<Xpert courseId={courseId} />, { preloadedState: errorState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: errorState });

await user.click(screen.queryByTestId('toggle-button'));

Expand All @@ -259,9 +262,10 @@ test('error message should disappear when messages cleared', async () => {
// TEMPORARY: This is simply to ensure that the tests pass by hiding the disclosure.
// I will remove this and write tests in a future pull request.
disclosureAcknowledged: true,
sidebarIsOpen: false,
},
};
render(<Xpert courseId={courseId} />, { preloadedState: errorState });
render(<Xpert courseId={courseId} contentToolsEnabled={false} />, { preloadedState: errorState });

await user.click(screen.queryByTestId('toggle-button'));

Expand Down

0 comments on commit 71ff0e2

Please sign in to comment.