2698ad422e
* Move shared components to a packages/ directory so they can be publish more sensibly * Iterate towards split out shared-components module * Move shared component source into src/ subdir * Fix up imports * Include shared components in babel-ing (again) * Remove now unused dependencies * Update import in storybook preview * ...except of course they aren't unused if we import the shared components by source * Ignore shared components deps * Add shared-components to i18n paths and upgrade web-i18n to version that supports doing so * Move storybook stuff to shared-components * Seems we don't need this anymore... * Remove unused deps and remove storybook plugin from eslint * Presumably working-directory is only valid on run steps * Ignore dep & run prettier * Prettier on knips.ts * Hopefully run in right dir * Remember how to software write * Okay... how about THIS way? * Oh right, they were git ignored. Sigh. * Add concurrently * Ignore in knip * Better? * Paaaaaaaackageeeeeeees * More packages * Move playwright snapshots * Still need a custom snapshots dir * Add eslint back * Oh, now knip sees them * Fix another import * Don't lint shared-components with everything else Okay, eslint & tsconfig are tied too closely for this to work and running tsc on the shared components will need its deps installing * Maybe lint shared components please? * Not quite * Remove storybook again Re-check if it does work without it * Remove storybook eslint plugin as we're not linting storybook here anymore * Remove this too * We do need it here though
53 lines
1.8 KiB
TypeScript
53 lines
1.8 KiB
TypeScript
/*
|
|
* 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 "./MockViewModel";
|
|
import { type ViewModel } from "./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} />;
|
|
}
|