Refactor EventPreview to shared MVVM (#33646)
* Refactor EventPreview to shared MVVM * Fix EventPreviewView export formatting * Snapshots images for stories * Deduplicate event preview formatting * Handle event preview update failures * Coalesce event preview updates * Wait for event preview test expectations
This commit is contained in:
@@ -43,6 +43,7 @@ export * from "./room/timeline/event-tile/EventTileView/DisambiguatedProfile";
|
||||
export * from "./room/timeline/event-tile/EventTileView/E2eMessageSharedIcon";
|
||||
export * from "./room/timeline/event-tile/EventTileView/E2ePadlock";
|
||||
export * from "./room/timeline/event-tile/EventTileView/EncryptionEventView";
|
||||
export * from "./room/timeline/event-tile/EventTileView/EventPreviewView";
|
||||
export * from "./room/timeline/event-tile/call";
|
||||
export * from "./room/timeline/event-tile/EventTileView/EventTileBubble";
|
||||
export * from "./room/timeline/event-tile/EventTileView/MJitsiWidgetEventView";
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.eventPreview {
|
||||
font: var(--cpd-font-body-sm-regular);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.eventPreview strong {
|
||||
font: var(--cpd-font-body-sm-semibold);
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { useMockedViewModel } from "../../../../../core/viewmodel";
|
||||
import { withViewDocs } from "../../../../../../.storybook/withViewDocs";
|
||||
import { EventPreviewView, type EventPreviewViewSnapshot } from "./EventPreviewView";
|
||||
|
||||
type WrapperProps = EventPreviewViewSnapshot & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const EventPreviewViewWrapperImpl = ({ className, ...snapshotProps }: WrapperProps): JSX.Element => {
|
||||
const vm = useMockedViewModel(snapshotProps, {});
|
||||
|
||||
return <EventPreviewView vm={vm} className={className} />;
|
||||
};
|
||||
|
||||
const EventPreviewViewWrapper = withViewDocs(EventPreviewViewWrapperImpl, EventPreviewView);
|
||||
|
||||
const meta = {
|
||||
title: "Timeline/EventTile/EventPreviewView",
|
||||
component: EventPreviewViewWrapper,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
isVisible: true,
|
||||
previewContent: "A short text message preview",
|
||||
previewTooltip: "A short text message preview",
|
||||
},
|
||||
} satisfies Meta<typeof EventPreviewViewWrapper>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithPrefix: Story = {
|
||||
args: {
|
||||
previewContent: (
|
||||
<>
|
||||
<strong>Image:</strong> city-map.png
|
||||
</>
|
||||
),
|
||||
previewTooltip: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const Hidden: Story = {
|
||||
args: {
|
||||
isVisible: false,
|
||||
},
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { composeStories } from "@storybook/react-vite";
|
||||
import { render, screen } from "@test-utils";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { MockViewModel } from "../../../../../core/viewmodel";
|
||||
import { EventPreviewView, type EventPreviewViewModel, type EventPreviewViewSnapshot } from "./EventPreviewView";
|
||||
import * as stories from "./EventPreviewView.stories";
|
||||
|
||||
const { Default, WithPrefix, Hidden } = composeStories(stories);
|
||||
|
||||
describe("EventPreviewView", () => {
|
||||
it("renders the default event preview", () => {
|
||||
const { container } = render(<Default />);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a prefixed event preview", () => {
|
||||
const { container } = render(<WithPrefix />);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not render when hidden", () => {
|
||||
render(<Hidden />);
|
||||
|
||||
expect(screen.queryByText("A short text message preview")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies custom span props without using native title tooltips", () => {
|
||||
const vm = new MockViewModel<EventPreviewViewSnapshot>({
|
||||
isVisible: true,
|
||||
previewContent: "Preview text",
|
||||
previewTooltip: "Preview text",
|
||||
}) as EventPreviewViewModel;
|
||||
|
||||
render(<EventPreviewView vm={vm} className="custom-preview" data-testid="event-preview" />);
|
||||
|
||||
expect(screen.getByTestId("event-preview")).toHaveClass("mx_EventPreview", "custom-preview");
|
||||
expect(screen.getByTestId("event-preview")).not.toHaveAttribute("title");
|
||||
});
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type ComponentPropsWithoutRef, type JSX, type ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
|
||||
import { type ViewModel, useViewModel } from "../../../../../core/viewmodel";
|
||||
import styles from "./EventPreviewView.module.css";
|
||||
|
||||
export interface EventPreviewViewSnapshot {
|
||||
/**
|
||||
* Controls whether the preview should render.
|
||||
*/
|
||||
isVisible: boolean;
|
||||
/**
|
||||
* Rendered preview content.
|
||||
*/
|
||||
previewContent?: ReactNode;
|
||||
/**
|
||||
* Optional styled tooltip text for the preview content.
|
||||
*/
|
||||
previewTooltip?: string;
|
||||
}
|
||||
|
||||
export type EventPreviewViewModel = ViewModel<EventPreviewViewSnapshot>;
|
||||
|
||||
type EventPreviewViewProps = Omit<ComponentPropsWithoutRef<"span">, "children" | "title"> & {
|
||||
/**
|
||||
* The view model for the event preview.
|
||||
*/
|
||||
vm: EventPreviewViewModel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a compact preview of an event.
|
||||
*/
|
||||
export function EventPreviewView({ vm, className, ...props }: Readonly<EventPreviewViewProps>): JSX.Element {
|
||||
const { isVisible, previewContent, previewTooltip } = useViewModel(vm);
|
||||
|
||||
if (!isVisible || !previewContent) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const preview = (
|
||||
<span {...props} className={classNames("mx_EventPreview", styles.eventPreview, className)}>
|
||||
{previewContent}
|
||||
</span>
|
||||
);
|
||||
|
||||
if (!previewTooltip) {
|
||||
return preview;
|
||||
}
|
||||
|
||||
return <Tooltip description={previewTooltip}>{preview}</Tooltip>;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`EventPreviewView > renders a prefixed event preview 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_EventPreview EventPreviewView-module_eventPreview"
|
||||
>
|
||||
<strong>
|
||||
Image:
|
||||
</strong>
|
||||
city-map.png
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EventPreviewView > renders the default event preview 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_EventPreview EventPreviewView-module_eventPreview"
|
||||
>
|
||||
A short text message preview
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
export { EventPreviewView, type EventPreviewViewModel, type EventPreviewViewSnapshot } from "./EventPreviewView";
|
||||
Reference in New Issue
Block a user