Replace ViewWrapper by useMockedViewModel (#31067)

* feat: replace `ViewWrapper` by `useMockedViewModel`

* feat: update existing story
This commit is contained in:
Florian Duros 2025-10-23 10:06:06 +02:00 committed by GitHub
parent 9be323dfd0
commit 0e6bacffed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 36 additions and 63 deletions

View File

@ -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} />;
}

View File

@ -9,18 +9,18 @@ import React, { type JSX } from "react";
import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite";
import {
AudioPlayerView,
type AudioPlayerViewActions,
type AudioPlayerViewSnapshot,
type AudioPlayerViewModel,
} from "./AudioPlayerView";
import { ViewWrapper } from "../../ViewWrapper";
import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView";
import { useMockedViewModel } from "../../useMockedViewModel";
type AudioPlayerProps = AudioPlayerViewSnapshot & AudioPlayerViewActions;
const AudioPlayerViewWrapper = (props: AudioPlayerProps): JSX.Element => (
<ViewWrapper<AudioPlayerViewSnapshot, AudioPlayerViewModel> Component={AudioPlayerView} props={props} />
);
const AudioPlayerViewWrapper = ({ togglePlay, onKeyDown, onSeekbarChange, ...rest }: AudioPlayerProps): JSX.Element => {
const vm = useMockedViewModel(rest, {
togglePlay,
onKeyDown,
onSeekbarChange,
});
return <AudioPlayerView vm={vm} />;
};
export default {
title: "Audio/AudioPlayerView",

View File

@ -28,5 +28,5 @@ export * from "./utils/numbers";
// MVVM
export * from "./viewmodel";
export * from "./ViewWrapper";
export * from "./useMockedViewModel";
export * from "./useViewModel";

View 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]);
}