Fix visual regressions around widget permissions (#10954)
* Add a Jest snapshot of AppPermission * Move the test inside 'for a pinned widget' category * Make only spinner message bold * Set font size specified with "mx_AppPermission_smallText" by default - Add "mx_AppPermission_largeText" for elements whose size has not been specified with mx_AppPermission_smallText - Create _AppWarning.pcss for AppWarning * Make AppPermission panel scrollable, keeping the content at the center * Run prettier * Use Heading component * Use Icon component * Fix the test
This commit is contained in:
parent
127b542233
commit
b40f29f04c
8 changed files with 251 additions and 70 deletions
|
@ -21,6 +21,7 @@
|
|||
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
||||
@import "./components/views/dialogs/polls/_PollListItemEnded.pcss";
|
||||
@import "./components/views/elements/_AppPermission.pcss";
|
||||
@import "./components/views/elements/_AppWarning.pcss";
|
||||
@import "./components/views/elements/_FilterDropdown.pcss";
|
||||
@import "./components/views/elements/_FilterTabGroup.pcss";
|
||||
@import "./components/views/elements/_LearnMore.pcss";
|
||||
|
|
|
@ -16,41 +16,29 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_AppPermission {
|
||||
> div {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mx_AppPermission_smallText {
|
||||
font-size: $font-12px;
|
||||
width: 100%; /* make mx_AppPermission fill width of mx_AppTileBody so that scroll bar appears on the edge */
|
||||
overflow-y: scroll;
|
||||
|
||||
.mx_AppPermission_content {
|
||||
margin-block: auto; /* place at the center */
|
||||
|
||||
> div {
|
||||
margin-block: 12px;
|
||||
}
|
||||
|
||||
.mx_AppPermission_bolder {
|
||||
.mx_AppPermission_content_bolder {
|
||||
font-weight: var(--font-semi-bold);
|
||||
}
|
||||
|
||||
.mx_AppPermission_helpIcon {
|
||||
margin-top: 1px;
|
||||
margin-right: 2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
.mx_TextWithTooltip_target--helpIcon {
|
||||
display: inline-block;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
background-color: $accent;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
mask-position: center;
|
||||
content: "";
|
||||
height: $font-14px; /* align with characters on the same line */
|
||||
vertical-align: middle;
|
||||
mask-image: url("$(res)/img/feather-customised/help-circle.svg");
|
||||
|
||||
.mx_Icon {
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
25
res/css/components/views/elements/_AppWarning.pcss
Normal file
25
res/css/components/views/elements/_AppWarning.pcss
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2023 Suguru Hirahara
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_AppWarning {
|
||||
font-size: $font-16px;
|
||||
justify-content: center;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
|
@ -311,14 +311,7 @@ limitations under the License.
|
|||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: $font-16px;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AppTile_loading {
|
||||
|
@ -326,7 +319,6 @@ limitations under the License.
|
|||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||
<g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||
<circle cx="6" cy="6" r="6"/>
|
||||
<path d="M4.254 4.2a1.8 1.8 0 0 1 3.498.6c0 1.2-1.8 1.8-1.8 1.8M6 8.991"/>
|
||||
</g>
|
||||
|
|
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 357 B |
|
@ -25,9 +25,11 @@ import WidgetUtils from "../../../utils/WidgetUtils";
|
|||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import Heading from "../typography/Heading";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import TextWithTooltip from "./TextWithTooltip";
|
||||
import { parseUrl } from "../../../utils/UrlUtils";
|
||||
import { Icon as HelpIcon } from "../../../../res/img/feather-customised/help-circle.svg";
|
||||
|
||||
interface IProps {
|
||||
url: string;
|
||||
|
@ -117,8 +119,9 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
|||
<TextWithTooltip
|
||||
tooltip={warningTooltipText}
|
||||
tooltipClass="mx_Tooltip--appPermission mx_Tooltip--appPermission--dark"
|
||||
class="mx_TextWithTooltip_target--helpIcon"
|
||||
>
|
||||
<span className="mx_AppPermission_helpIcon" />
|
||||
<HelpIcon className="mx_Icon mx_Icon_12" />
|
||||
</TextWithTooltip>
|
||||
);
|
||||
|
||||
|
@ -139,14 +142,15 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
|||
|
||||
return (
|
||||
<div className="mx_AppPermission">
|
||||
<div className="mx_AppPermission_bolder mx_AppPermission_smallText">{_t("Widget added by")}</div>
|
||||
<div className="mx_AppPermission_content">
|
||||
<div className="mx_AppPermission_content_bolder">{_t("Widget added by")}</div>
|
||||
<div>
|
||||
{avatar}
|
||||
<h4 className="mx_AppPermission_bolder">{displayName}</h4>
|
||||
<div className="mx_AppPermission_smallText">{userId}</div>
|
||||
<Heading size="h4">{displayName}</Heading>
|
||||
<div>{userId}</div>
|
||||
</div>
|
||||
<div className="mx_AppPermission_smallText">{warning}</div>
|
||||
<div className="mx_AppPermission_smallText">
|
||||
<div>{warning}</div>
|
||||
<div>
|
||||
{_t("This widget may use cookies.")} {encryptionWarning}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -155,6 +159,7 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
|||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,11 @@ jest.mock("../../../../src/stores/OwnProfileStore", () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// Fake random strings to give a predictable snapshot
|
||||
jest.mock("matrix-js-sdk/src/randomstring", () => ({
|
||||
randomString: () => "abdefghi",
|
||||
}));
|
||||
|
||||
describe("AppTile", () => {
|
||||
let cli: MatrixClient;
|
||||
let r1: Room;
|
||||
|
@ -387,6 +392,45 @@ describe("AppTile", () => {
|
|||
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Center);
|
||||
});
|
||||
|
||||
it("should render permission request", () => {
|
||||
jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation((lifecycleEvent, opts, widgetInfo) => {
|
||||
if (lifecycleEvent === WidgetLifecycle.PreLoadRequest && (widgetInfo as WidgetInfo).id === app1.id) {
|
||||
(opts as ApprovalOpts).approved = false;
|
||||
}
|
||||
});
|
||||
|
||||
// userId and creatorUserId are different
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
const { container, asFragment } = renderResult;
|
||||
|
||||
expect(container.querySelector(".mx_Spinner")).toBeFalsy();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
expect(renderResult.queryByRole("button", { name: "Continue" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not display 'Continue' button on permission load", () => {
|
||||
jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation((lifecycleEvent, opts, widgetInfo) => {
|
||||
if (lifecycleEvent === WidgetLifecycle.PreLoadRequest && (widgetInfo as WidgetInfo).id === app1.id) {
|
||||
(opts as ApprovalOpts).approved = true;
|
||||
}
|
||||
});
|
||||
|
||||
// userId and creatorUserId are different
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
expect(renderResult.queryByRole("button", { name: "Continue" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("for a maximised (centered) widget", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockImplementation(
|
||||
|
@ -446,21 +490,4 @@ describe("AppTile", () => {
|
|||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("for a pinned widget permission load", () => {
|
||||
jest.spyOn(ModuleRunner.instance, "invoke").mockImplementation((lifecycleEvent, opts, widgetInfo) => {
|
||||
if (lifecycleEvent === WidgetLifecycle.PreLoadRequest && (widgetInfo as WidgetInfo).id === app1.id) {
|
||||
(opts as ApprovalOpts).approved = true;
|
||||
}
|
||||
});
|
||||
|
||||
// userId and creatorUserId are different
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
expect(renderResult.queryByRole("button", { name: "Continue" })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -163,6 +163,149 @@ exports[`AppTile for a pinned widget should render 1`] = `
|
|||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`AppTile for a pinned widget should render permission request 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_AppTile"
|
||||
id="1"
|
||||
>
|
||||
<div
|
||||
class="mx_AppTileMenuBar"
|
||||
>
|
||||
<span
|
||||
class="mx_AppTileMenuBar_title"
|
||||
style="pointer-events: none;"
|
||||
>
|
||||
<span>
|
||||
<img
|
||||
alt=""
|
||||
class="mx_BaseAvatar mx_BaseAvatar_image mx_WidgetAvatar"
|
||||
data-testid="avatar-img"
|
||||
loading="lazy"
|
||||
src="image-file-stub"
|
||||
style="width: 20px; height: 20px;"
|
||||
/>
|
||||
<b>
|
||||
Example 1
|
||||
</b>
|
||||
<span />
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="mx_AppTileMenuBar_widgets"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Un-maximise"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_12"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Minimise"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_12"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Options"
|
||||
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Options"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_12"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppTileBody"
|
||||
>
|
||||
<div
|
||||
class="mx_AppPermission"
|
||||
>
|
||||
<div
|
||||
class="mx_AppPermission_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AppPermission_content_bolder"
|
||||
>
|
||||
Widget added by
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 24.7px; width: 38px; line-height: 38px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
loading="lazy"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 38px; height: 38px;"
|
||||
/>
|
||||
</span>
|
||||
<h4
|
||||
class="mx_Heading_h4"
|
||||
>
|
||||
@userAnother
|
||||
</h4>
|
||||
<div />
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Using this widget may share data
|
||||
<div
|
||||
aria-describedby="mx_TooltipTarget_abdefghi"
|
||||
class="mx_TextWithTooltip_target mx_TextWithTooltip_target--helpIcon"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_12"
|
||||
/>
|
||||
</div>
|
||||
with example.com.
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
This widget may use cookies.
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Continue
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`AppTile preserves non-persisted widget on container move 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
|
|
Loading…
Reference in a new issue