diff --git a/src/Roles.ts b/src/Roles.ts index ae0d316d30..77c50fe64c 100644 --- a/src/Roles.ts +++ b/src/Roles.ts @@ -16,7 +16,7 @@ limitations under the License. import { _t } from './languageHandler'; -export function levelRoleMap(usersDefault: number) { +export function levelRoleMap(usersDefault: number): Record { return { undefined: _t('Default'), 0: _t('Restricted'), diff --git a/src/components/views/elements/PowerSelector.tsx b/src/components/views/elements/PowerSelector.tsx index 3a9e87d158..8b251b91a5 100644 --- a/src/components/views/elements/PowerSelector.tsx +++ b/src/components/views/elements/PowerSelector.tsx @@ -44,14 +44,13 @@ interface IProps { } interface IState { - levelRoleMap: {}; + levelRoleMap: Partial>; // List of power levels to show in the drop-down options: number[]; customValue: number; selectValue: number | string; custom?: boolean; - customLevel?: number; } export default class PowerSelector extends React.Component { @@ -101,7 +100,7 @@ export default class PowerSelector extends React.Component { levelRoleMap, options, custom: isCustom, - customLevel: newProps.value, + customValue: newProps.value, selectValue: isCustom ? CUSTOM_VALUE : newProps.value, }); } @@ -125,7 +124,11 @@ export default class PowerSelector extends React.Component { event.preventDefault(); event.stopPropagation(); - this.props.onChange(this.state.customValue, this.props.powerLevelKey); + if (Number.isFinite(this.state.customValue)) { + this.props.onChange(this.state.customValue, this.props.powerLevelKey); + } else { + this.initStateFromProps(this.props); // reset, invalid input + } }; private onCustomKeyDown = (event: React.KeyboardEvent): void => { diff --git a/test/components/views/elements/PowerSelector-test.tsx b/test/components/views/elements/PowerSelector-test.tsx new file mode 100644 index 0000000000..9367d0b089 --- /dev/null +++ b/test/components/views/elements/PowerSelector-test.tsx @@ -0,0 +1,62 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { fireEvent, render, screen } from "@testing-library/react"; + +import PowerSelector from "../../../../src/components/views/elements/PowerSelector"; + +describe('', () => { + it("should reset back to custom value when custom input is blurred blank", async () => { + const fn = jest.fn(); + render(); + + const input = screen.getByLabelText("Power level"); + fireEvent.change(input, { target: { value: "" } }); + fireEvent.blur(input); + + await screen.findByDisplayValue(25); + expect(fn).not.toHaveBeenCalled(); + }); + + it("should reset back to preset value when custom input is blurred blank", async () => { + const fn = jest.fn(); + render(); + + const select = screen.getByLabelText("Power level"); + fireEvent.change(select, { target: { value: "SELECT_VALUE_CUSTOM" } }); + + const input = screen.getByLabelText("Power level"); + fireEvent.change(input, { target: { value: "" } }); + fireEvent.blur(input); + + const option = await screen.findByText("Moderator"); + expect(option.selected).toBeTruthy(); + expect(fn).not.toHaveBeenCalled(); + }); + + it("should call onChange when custom input is blurred with a number in it", async () => { + const fn = jest.fn(); + render(); + + const input = screen.getByLabelText("Power level"); + fireEvent.change(input, { target: { value: 40 } }); + fireEvent.blur(input); + + await screen.findByDisplayValue(40); + expect(fn).toHaveBeenCalledWith(40, "key"); + }); +});