mirror of https://github.com/vector-im/riot-web
				
				
				
			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 testpull/28788/head^2
							parent
							
								
									127b542233
								
							
						
					
					
						commit
						b40f29f04c
					
				| 
						 | 
				
			
			@ -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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 New Issue