Skip to content

Commit

Permalink
fix: masquerade dropdown not showing current selection (#30)
Browse files Browse the repository at this point in the history
* feat: remove child components from state and use data instead

(cherry picked from commit 2d9807e)

* fix: change active selection based on user input

(cherry picked from commit d72eb23)

* test: add test cases

(cherry picked from commit 59d9db5)
  • Loading branch information
kaustavb12 authored Aug 14, 2024
1 parent 4b4f3fd commit 702c962
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 22 deletions.
50 changes: 29 additions & 21 deletions src/instructor-toolbar/masquerade-widget/MasqueradeWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class MasqueradeWidget extends Component {
this.state = {
autoFocus: false,
masquerade: 'Staff',
options: [],
active: {},
available: [],
shouldShowUserNameInput: false,
masqueradeUsername: null,
};
Expand Down Expand Up @@ -58,41 +59,49 @@ class MasqueradeWidget extends Component {
}

onSuccess(data) {
const options = this.parseAvailableOptions(data);
const { active, available } = this.parseAvailableOptions(data);
this.setState({
options,
active,
available,
});
}

getOptions() {
const options = this.state.available.map((group) => (
<MasqueradeWidgetOption
groupId={group.groupId}
groupName={group.name}
key={group.name}
role={group.role}
selected={this.state.active}
userName={group.userName}
userPartitionId={group.userPartitionId}
userNameInputToggle={(...args) => this.toggle(...args)}
onSubmit={(payload) => this.onSubmit(payload)}
/>
));
return options;
}

clearError() {
this.props.onError('');
}

toggle(show) {
toggle(show, groupId, groupName, role, userName, userPartitionId) {
this.setState(prevState => ({
autoFocus: true,
masquerade: 'Specific Student...',
masquerade: groupName,
shouldShowUserNameInput: show === undefined ? !prevState.shouldShowUserNameInput : show,
active: {
...prevState.active, groupId, role, userName, userPartitionId,
},
}));
}

parseAvailableOptions(postData) {
const data = postData || {};
const active = data.active || {};
const available = data.available || [];
const options = available.map((group) => (
<MasqueradeWidgetOption
groupId={group.groupId}
groupName={group.name}
key={group.name}
role={group.role}
selected={active}
userName={group.userName}
userPartitionId={group.userPartitionId}
userNameInputToggle={(...args) => this.toggle(...args)}
onSubmit={(payload) => this.onSubmit(payload)}
/>
));
if (active.userName) {
this.setState({
autoFocus: false,
Expand All @@ -105,14 +114,13 @@ class MasqueradeWidget extends Component {
} else if (active.role === 'student') {
this.setState({ masquerade: 'Learner' });
}
return options;
return { active, available };
}

render() {
const {
autoFocus,
masquerade,
options,
shouldShowUserNameInput,
masqueradeUsername,
} = this.state;
Expand All @@ -126,7 +134,7 @@ class MasqueradeWidget extends Component {
{masquerade}
</Dropdown.Toggle>
<Dropdown.Menu>
{options}
{this.getOptions()}
</Dropdown.Menu>
</Dropdown>
</div>
Expand Down
137 changes: 137 additions & 0 deletions src/instructor-toolbar/masquerade-widget/MasqueradeWidget.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React from 'react';
import { getAllByRole } from '@testing-library/dom';
import { act } from '@testing-library/react';
import { getConfig } from '@edx/frontend-platform';
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import MasqueradeWidget from './MasqueradeWidget';
import {
render, screen, fireEvent, initializeTestStore, waitFor, logUnhandledRequests,
} from '../../setupTest';

const originalConfig = jest.requireActual('@edx/frontend-platform').getConfig();
jest.mock('@edx/frontend-platform', () => ({
...jest.requireActual('@edx/frontend-platform'),
getConfig: jest.fn(),
}));
getConfig.mockImplementation(() => originalConfig);

describe('Masquerade Widget Dropdown', () => {
let mockData;
let courseware;
let mockResponse;
let axiosMock;
let masqueradeUrl;
const masqueradeOptions = [
{
name: 'Staff',
role: 'staff',
},
{
name: 'Specific Student...',
role: 'student',
user_name: '',
},
{
group_id: 1,
name: 'Audit',
role: 'student',
user_partition_id: 50,
},
];

beforeAll(async () => {
const store = await initializeTestStore();
courseware = store.getState().courseware;
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
masqueradeUrl = `${getConfig().LMS_BASE_URL}/courses/${courseware.courseId}/masquerade`;
mockData = {
courseId: courseware.courseId,
onError: () => {},
};
});

beforeEach(() => {
mockResponse = {
success: true,
active: {
course_key: courseware.courseId,
group_id: null,
role: 'staff',
user_name: null,
user_partition_id: null,
group_name: null,
},
available: masqueradeOptions,
};
axiosMock.reset();
axiosMock.onGet(masqueradeUrl).reply(200, mockResponse);
logUnhandledRequests(axiosMock);
});

it('renders masquerade name correctly', async () => {
render(<MasqueradeWidget {...mockData} />);
await waitFor(() => expect(axiosMock.history.get).toHaveLength(1));
expect(screen.getByRole('button')).toHaveTextContent('Staff');
});

masqueradeOptions.forEach((option) => {
it(`marks role ${option.role} as active`, async () => {
const active = {
course_key: courseware.courseId,
group_id: option.group_id ?? null,
role: option.role,
user_name: option.user_name ?? null,
user_partition_id: option.user_partition_id ?? null,
group_name: null,
};

mockResponse = {
success: true,
active,
available: masqueradeOptions,
};

axiosMock.reset();
axiosMock.onGet(masqueradeUrl).reply(200, mockResponse);

const { container } = render(<MasqueradeWidget {...mockData} />);
const dropdownToggle = container.querySelector('.dropdown-toggle');
await act(async () => {
await fireEvent.click(dropdownToggle);
});
const dropdownMenu = container.querySelector('.dropdown-menu');
getAllByRole(dropdownMenu, 'button', { hidden: true }).forEach(button => {
if (button.textContent === option.name) {
expect(button).toHaveClass('active');
} else {
expect(button).not.toHaveClass('active');
}
});
});
});

it('handles the clicks with toggle', async () => {
const { container } = render(<MasqueradeWidget {...mockData} />);
await waitFor(() => expect(axiosMock.history.get).toHaveLength(1));

const dropdownToggle = container.querySelector('.dropdown-toggle');
await act(async () => {
await fireEvent.click(dropdownToggle);
});
const dropdownMenu = container.querySelector('.dropdown-menu');
const studentOption = getAllByRole(dropdownMenu, 'button', { hidden: true }).filter(
button => (button.textContent === 'Specific Student...'),
)[0];
await act(async () => {
await fireEvent.click(studentOption);
});
getAllByRole(dropdownMenu, 'button', { hidden: true }).forEach(button => {
if (button.textContent === 'Specific Student...') {
expect(button).toHaveClass('active');
} else {
expect(button).not.toHaveClass('active');
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ class MasqueradeWidgetOption extends Component {
event.target.parentNode.parentNode.click();
const {
groupId,
groupName,
role,
userName,
userPartitionId,
userNameInputToggle,
} = this.props;
const payload = {};
if (userName || userName === '') {
userNameInputToggle(true);
userNameInputToggle(true, groupId, groupName, role, userName, userPartitionId);
return false;
}
if (role) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react';
import { getAllByRole } from '@testing-library/dom';
import { act } from '@testing-library/react';
import { getConfig } from '@edx/frontend-platform';
import MasqueradeWidgetOption from './MasqueradeWidgetOption';
import {
render, fireEvent, initializeTestStore,
} from '../../setupTest';

const originalConfig = jest.requireActual('@edx/frontend-platform').getConfig();
jest.mock('@edx/frontend-platform', () => ({
...jest.requireActual('@edx/frontend-platform'),
getConfig: jest.fn(),
}));
getConfig.mockImplementation(() => originalConfig);

describe('Masquerade Widget Dropdown', () => {
let courseware;
let mockDataStaff;
let mockDataStudent;
let active;

beforeAll(async () => {
const store = await initializeTestStore();
courseware = store.getState().courseware;
active = {
courseKey: courseware.courseId,
groupId: null,
role: 'staff',
userName: null,
userPartitionId: null,
groupName: null,
};
mockDataStaff = {
groupId: null,
groupName: 'Staff',
key: 'Staff',
role: 'staff',
selected: active,
userName: null,
userPartitionId: null,
userNameInputToggle: () => {},
onSubmit: () => {},
};
mockDataStudent = {
groupId: null,
groupName: 'Specific Student...',
key: 'Specific Student...',
role: 'student',
selected: active,
userName: '',
userPartitionId: null,
userNameInputToggle: () => {},
onSubmit: () => {},
};
Object.defineProperty(global, 'location', {
configurable: true,
value: { reload: jest.fn() },
});
});

it('renders masquerade active option correctly', async () => {
const { container } = render(<MasqueradeWidgetOption {...mockDataStaff} />);
const button = getAllByRole(container, 'button', { hidden: true })[0];
expect(button).toHaveTextContent('Staff');
expect(button).toHaveClass('active');
});

it('renders masquerade inactive option correctly', async () => {
const { container } = render(<MasqueradeWidgetOption {...mockDataStudent} />);
const button = getAllByRole(container, 'button', { hidden: true })[0];
expect(button).toHaveTextContent('Specific Student...');
expect(button).not.toHaveClass('active');
});

it('handles the clicks regular option', () => {
const onSubmit = jest.fn().mockImplementation(() => Promise.resolve());
const { container } = render(<MasqueradeWidgetOption {...mockDataStaff} onSubmit={onSubmit} />);
const button = getAllByRole(container, 'button', { hidden: true })[0];
act(() => {
fireEvent.click(button);
});
expect(onSubmit).toHaveBeenCalled();
});

it('handles the clicks student option', () => {
const userNameInputToggle = jest.fn().mockImplementation(() => Promise.resolve());
const { container } = render(
<MasqueradeWidgetOption {...mockDataStudent} userNameInputToggle={userNameInputToggle} />,
);
const button = getAllByRole(container, 'button', { hidden: true })[0];
act(() => {
fireEvent.click(button);
});
expect(userNameInputToggle).toHaveBeenCalled();
});
});

0 comments on commit 702c962

Please sign in to comment.