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/_PollListItem.pcss";
|
||||||
@import "./components/views/dialogs/polls/_PollListItemEnded.pcss";
|
@import "./components/views/dialogs/polls/_PollListItemEnded.pcss";
|
||||||
@import "./components/views/elements/_AppPermission.pcss";
|
@import "./components/views/elements/_AppPermission.pcss";
|
||||||
|
@import "./components/views/elements/_AppWarning.pcss";
|
||||||
@import "./components/views/elements/_FilterDropdown.pcss";
|
@import "./components/views/elements/_FilterDropdown.pcss";
|
||||||
@import "./components/views/elements/_FilterTabGroup.pcss";
|
@import "./components/views/elements/_FilterTabGroup.pcss";
|
||||||
@import "./components/views/elements/_LearnMore.pcss";
|
@import "./components/views/elements/_LearnMore.pcss";
|
||||||
|
|
|
@ -16,41 +16,29 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_AppPermission {
|
.mx_AppPermission {
|
||||||
> div {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AppPermission_smallText {
|
|
||||||
font-size: $font-12px;
|
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);
|
font-weight: var(--font-semi-bold);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppPermission_helpIcon {
|
.mx_TextWithTooltip_target--helpIcon {
|
||||||
margin-top: 1px;
|
|
||||||
margin-right: 2px;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
height: $font-14px; /* align with characters on the same line */
|
||||||
&::before {
|
|
||||||
display: inline-block;
|
|
||||||
background-color: $accent;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: 12px;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
mask-position: center;
|
|
||||||
content: "";
|
|
||||||
vertical-align: middle;
|
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;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: $font-16px;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTile_loading {
|
.mx_AppTile_loading {
|
||||||
|
@ -326,7 +319,6 @@ limitations under the License.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-weight: bold;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
|
<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"/>
|
<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"/>
|
<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>
|
</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 { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import MemberAvatar from "../avatars/MemberAvatar";
|
import MemberAvatar from "../avatars/MemberAvatar";
|
||||||
import BaseAvatar from "../avatars/BaseAvatar";
|
import BaseAvatar from "../avatars/BaseAvatar";
|
||||||
|
import Heading from "../typography/Heading";
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import TextWithTooltip from "./TextWithTooltip";
|
import TextWithTooltip from "./TextWithTooltip";
|
||||||
import { parseUrl } from "../../../utils/UrlUtils";
|
import { parseUrl } from "../../../utils/UrlUtils";
|
||||||
|
import { Icon as HelpIcon } from "../../../../res/img/feather-customised/help-circle.svg";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -117,8 +119,9 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
||||||
<TextWithTooltip
|
<TextWithTooltip
|
||||||
tooltip={warningTooltipText}
|
tooltip={warningTooltipText}
|
||||||
tooltipClass="mx_Tooltip--appPermission mx_Tooltip--appPermission--dark"
|
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>
|
</TextWithTooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -139,14 +142,15 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_AppPermission">
|
<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>
|
<div>
|
||||||
{avatar}
|
{avatar}
|
||||||
<h4 className="mx_AppPermission_bolder">{displayName}</h4>
|
<Heading size="h4">{displayName}</Heading>
|
||||||
<div className="mx_AppPermission_smallText">{userId}</div>
|
<div>{userId}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_AppPermission_smallText">{warning}</div>
|
<div>{warning}</div>
|
||||||
<div className="mx_AppPermission_smallText">
|
<div>
|
||||||
{_t("This widget may use cookies.")} {encryptionWarning}
|
{_t("This widget may use cookies.")} {encryptionWarning}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -155,6 +159,7 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</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", () => {
|
describe("AppTile", () => {
|
||||||
let cli: MatrixClient;
|
let cli: MatrixClient;
|
||||||
let r1: Room;
|
let r1: Room;
|
||||||
|
@ -387,6 +392,45 @@ describe("AppTile", () => {
|
||||||
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Center);
|
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", () => {
|
describe("for a maximised (centered) widget", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockImplementation(
|
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockImplementation(
|
||||||
|
@ -446,21 +490,4 @@ describe("AppTile", () => {
|
||||||
expect(asFragment()).toMatchSnapshot();
|
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>
|
</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`] = `
|
exports[`AppTile preserves non-persisted widget on container move 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
|
|
Loading…
Reference in a new issue