mirror of
https://github.com/vector-im/element-web.git
synced 2025-11-29 14:31:22 +01:00
Replace ViewWrapper by useMockedViewModel (#31067)
* feat: replace `ViewWrapper` by `useMockedViewModel` * feat: update existing story
This commit is contained in:
parent
9be323dfd0
commit
0e6bacffed
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector 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, useMemo, type ComponentType } from "react";
|
|
||||||
import { omitBy, pickBy } from "lodash";
|
|
||||||
|
|
||||||
import { MockViewModel } from "./viewmodel/MockViewModel";
|
|
||||||
import { type ViewModel } from "./viewmodel/ViewModel";
|
|
||||||
|
|
||||||
interface ViewWrapperProps<V> {
|
|
||||||
/**
|
|
||||||
* The component to render, which should accept a `vm` prop of type `V`.
|
|
||||||
*/
|
|
||||||
Component: ComponentType<{ vm: V }>;
|
|
||||||
/**
|
|
||||||
* The props to pass to the component, which can include both snapshot data and actions.
|
|
||||||
*/
|
|
||||||
props: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper component that creates a view model instance and passes it to the specified component.
|
|
||||||
* This is useful for testing components in isolation with a mocked view model and allows to use primitive types in stories.
|
|
||||||
*
|
|
||||||
* Props is parsed and split into snapshot and actions. Where values that are functions (`typeof Function`) are considered actions and the rest is considered the snapshot.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <ViewWrapper<SnapshotType, ViewModelType> props={Snapshot&Actions} Component={MyComponent} />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function ViewWrapper<T, V extends ViewModel<T>>({
|
|
||||||
props,
|
|
||||||
Component,
|
|
||||||
}: Readonly<ViewWrapperProps<V>>): JSX.Element {
|
|
||||||
const vm = useMemo(() => {
|
|
||||||
const isFunction = (value: any): value is typeof Function => typeof value === typeof Function;
|
|
||||||
const snapshot = omitBy(props, isFunction) as T;
|
|
||||||
const actions = pickBy(props, isFunction);
|
|
||||||
|
|
||||||
const vm = new MockViewModel<T>(snapshot);
|
|
||||||
Object.assign(vm, actions);
|
|
||||||
|
|
||||||
return vm as unknown as V;
|
|
||||||
}, [props]);
|
|
||||||
|
|
||||||
return <Component vm={vm} />;
|
|
||||||
}
|
|
||||||
@ -9,18 +9,18 @@ import React, { type JSX } from "react";
|
|||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
|
|
||||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||||
import {
|
import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView";
|
||||||
AudioPlayerView,
|
import { useMockedViewModel } from "../../useMockedViewModel";
|
||||||
type AudioPlayerViewActions,
|
|
||||||
type AudioPlayerViewSnapshot,
|
|
||||||
type AudioPlayerViewModel,
|
|
||||||
} from "./AudioPlayerView";
|
|
||||||
import { ViewWrapper } from "../../ViewWrapper";
|
|
||||||
|
|
||||||
type AudioPlayerProps = AudioPlayerViewSnapshot & AudioPlayerViewActions;
|
type AudioPlayerProps = AudioPlayerViewSnapshot & AudioPlayerViewActions;
|
||||||
const AudioPlayerViewWrapper = (props: AudioPlayerProps): JSX.Element => (
|
const AudioPlayerViewWrapper = ({ togglePlay, onKeyDown, onSeekbarChange, ...rest }: AudioPlayerProps): JSX.Element => {
|
||||||
<ViewWrapper<AudioPlayerViewSnapshot, AudioPlayerViewModel> Component={AudioPlayerView} props={props} />
|
const vm = useMockedViewModel(rest, {
|
||||||
);
|
togglePlay,
|
||||||
|
onKeyDown,
|
||||||
|
onSeekbarChange,
|
||||||
|
});
|
||||||
|
return <AudioPlayerView vm={vm} />;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Audio/AudioPlayerView",
|
title: "Audio/AudioPlayerView",
|
||||||
|
|||||||
@ -28,5 +28,5 @@ export * from "./utils/numbers";
|
|||||||
|
|
||||||
// MVVM
|
// MVVM
|
||||||
export * from "./viewmodel";
|
export * from "./viewmodel";
|
||||||
export * from "./ViewWrapper";
|
export * from "./useMockedViewModel";
|
||||||
export * from "./useViewModel";
|
export * from "./useViewModel";
|
||||||
|
|||||||
25
packages/shared-components/src/useMockedViewModel.ts
Normal file
25
packages/shared-components/src/useMockedViewModel.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector 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 { useMemo } from "react";
|
||||||
|
|
||||||
|
import { MockViewModel, type ViewModel } from "./viewmodel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook helper to return a mocked view model created with the given snapshot and actions.
|
||||||
|
* This is useful for testing components in isolation with a mocked view model and allows to use primitive types in stories.
|
||||||
|
*
|
||||||
|
* @param snapshot
|
||||||
|
* @param actions
|
||||||
|
*/
|
||||||
|
export function useMockedViewModel<S, A>(snapshot: S, actions: A): ViewModel<S> & A {
|
||||||
|
return useMemo(() => {
|
||||||
|
const vm = new MockViewModel<S>(snapshot);
|
||||||
|
Object.assign(vm, actions);
|
||||||
|
return vm as unknown as ViewModel<S> & A;
|
||||||
|
}, [snapshot, actions]);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user