From 2598e4ea22fd8af586cec3536c1da302c88fbaf5 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Wed, 15 Oct 2025 19:19:12 +0530 Subject: [PATCH] Move view model code to shared components package (#31024) * Remove vm related code from element-web/src * Add and export view model code from package * Update imports * Rewrite vm tests using vitest * Add github action to run vm tests * Fix lint errors * Mvoe tests over to jest * Try fixing code coverage * Second attempt at fixing code coverage --- jest.config.ts | 1 + packages/shared-components/package.json | 1 - packages/shared-components/src/ViewWrapper.tsx | 4 ++-- .../audio/AudioPlayerView/AudioPlayerView.test.tsx | 2 +- .../src/audio/AudioPlayerView/AudioPlayerView.tsx | 2 +- .../TextualEventView/TextualEventView.stories.tsx | 2 +- .../TextualEventView/TextualEventView.tsx | 2 +- packages/shared-components/src/index.ts | 3 +-- packages/shared-components/src/useViewModel.ts | 2 +- .../src/viewmodel}/BaseViewModel.ts | 2 +- .../shared-components/src/viewmodel}/Disposables.ts | 0 .../src/{ => viewmodel}/MockViewModel.ts | 0 .../shared-components/src/viewmodel}/Snapshot.ts | 0 .../src/{ => viewmodel}/ViewModel.ts | 0 .../src/viewmodel}/ViewModelSubscriptions.ts | 0 packages/shared-components/src/viewmodel/index.ts | 13 +++++++++++++ .../src/viewmodel/tests/Disposables.test.ts | 2 +- .../src/viewmodel/tests/Snapshot.test.ts | 2 +- sonar-project.properties | 2 +- src/viewmodels/audio/AudioPlayerViewModel.ts | 2 +- src/viewmodels/event-tiles/TextualEventViewModel.ts | 2 +- yarn.lock | 2 +- 22 files changed, 29 insertions(+), 17 deletions(-) rename {src/viewmodels/base => packages/shared-components/src/viewmodel}/BaseViewModel.ts (94%) rename {src/viewmodels/base => packages/shared-components/src/viewmodel}/Disposables.ts (100%) rename packages/shared-components/src/{ => viewmodel}/MockViewModel.ts (100%) rename {src/viewmodels/base => packages/shared-components/src/viewmodel}/Snapshot.ts (100%) rename packages/shared-components/src/{ => viewmodel}/ViewModel.ts (100%) rename {src/viewmodels/base => packages/shared-components/src/viewmodel}/ViewModelSubscriptions.ts (100%) create mode 100644 packages/shared-components/src/viewmodel/index.ts rename test/viewmodels/base/Disposables-test.ts => packages/shared-components/src/viewmodel/tests/Disposables.test.ts (95%) rename test/viewmodels/base/Snapshot-test.ts => packages/shared-components/src/viewmodel/tests/Snapshot.test.ts (95%) diff --git a/jest.config.ts b/jest.config.ts index 82f169b9de..e6c29b792d 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -46,6 +46,7 @@ const config: Config = { ], collectCoverageFrom: [ "/src/**/*.{js,ts,tsx}", + "/packages/**/*.{js,ts,tsx}", // getSessionLock is piped into a different JS context via stringification, and the coverage functionality is // not available in that contest. So, turn off coverage instrumentation for it. "!/src/utils/SessionLock.ts", diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index df770b1491..3d68950299 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -54,7 +54,6 @@ "@storybook/test-runner": "^0.23.0", "concurrently": "^9.2.1", "eslint": "8", - "jest-image-snapshot": "^6.5.1", "eslint-plugin-storybook": "^9.1.10", "jest-image-snapshot": "^6.5.1", "patch-package": "^8.0.1", diff --git a/packages/shared-components/src/ViewWrapper.tsx b/packages/shared-components/src/ViewWrapper.tsx index 57b81bd5b9..d0d445ea5c 100644 --- a/packages/shared-components/src/ViewWrapper.tsx +++ b/packages/shared-components/src/ViewWrapper.tsx @@ -8,8 +8,8 @@ import React, { type JSX, useMemo, type ComponentType } from "react"; import { omitBy, pickBy } from "lodash"; -import { MockViewModel } from "./MockViewModel"; -import { type ViewModel } from "./ViewModel"; +import { MockViewModel } from "./viewmodel/MockViewModel"; +import { type ViewModel } from "./viewmodel/ViewModel"; interface ViewWrapperProps { /** diff --git a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx index 3f08eec28c..018b388f6b 100644 --- a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx +++ b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx @@ -13,7 +13,7 @@ import { fireEvent } from "@testing-library/dom"; import * as stories from "./AudioPlayerView.stories.tsx"; import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView"; -import { MockViewModel } from "../../MockViewModel"; +import { MockViewModel } from "../../viewmodel/MockViewModel.ts"; const { Default, NoMediaName, NoSize, HasError } = composeStories(stories); diff --git a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx index f963c8b2cf..29fb02ba34 100644 --- a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx +++ b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx @@ -7,7 +7,7 @@ import React, { type ChangeEventHandler, type JSX, type KeyboardEventHandler, type MouseEventHandler } from "react"; -import { type ViewModel } from "../../ViewModel"; +import { type ViewModel } from "../../viewmodel/ViewModel"; import { useViewModel } from "../../useViewModel"; import { MediaBody } from "../../message-body/MediaBody"; import { Flex } from "../../utils/Flex"; diff --git a/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.stories.tsx b/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.stories.tsx index 836641a84c..0c89ce24d6 100644 --- a/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.stories.tsx +++ b/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.stories.tsx @@ -9,7 +9,7 @@ import React from "react"; import { type Meta, type StoryFn } from "@storybook/react-vite"; import { TextualEventView as TextualEventComponent } from "./TextualEventView"; -import { MockViewModel } from "../../MockViewModel"; +import { MockViewModel } from "../../viewmodel/MockViewModel"; export default { title: "Event/TextualEvent", diff --git a/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.tsx b/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.tsx index fa18a4c599..3d9e4ac109 100644 --- a/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.tsx +++ b/packages/shared-components/src/event-tiles/TextualEventView/TextualEventView.tsx @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. import React, { type ReactNode, type JSX } from "react"; -import { type ViewModel } from "../../ViewModel"; +import { type ViewModel } from "../../viewmodel/ViewModel"; import { useViewModel } from "../../useViewModel"; export type TextualEventViewSnapshot = { diff --git a/packages/shared-components/src/index.ts b/packages/shared-components/src/index.ts index 91bf777cf3..01fb99040b 100644 --- a/packages/shared-components/src/index.ts +++ b/packages/shared-components/src/index.ts @@ -27,7 +27,6 @@ export * from "./utils/DateUtils"; export * from "./utils/numbers"; // MVVM +export * from "./viewmodel"; export * from "./ViewWrapper"; -export type * from "./ViewModel"; export * from "./useViewModel"; -export * from "./MockViewModel"; diff --git a/packages/shared-components/src/useViewModel.ts b/packages/shared-components/src/useViewModel.ts index ef7b8ec0da..20c7070bff 100644 --- a/packages/shared-components/src/useViewModel.ts +++ b/packages/shared-components/src/useViewModel.ts @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. import { useSyncExternalStore } from "react"; -import { type ViewModel } from "./ViewModel"; +import { type ViewModel } from "./viewmodel/ViewModel"; /** * A small wrapper around useSyncExternalStore to use a view model in a shared component view diff --git a/src/viewmodels/base/BaseViewModel.ts b/packages/shared-components/src/viewmodel/BaseViewModel.ts similarity index 94% rename from src/viewmodels/base/BaseViewModel.ts rename to packages/shared-components/src/viewmodel/BaseViewModel.ts index 1bd58b9196..ffb961d8e0 100644 --- a/src/viewmodels/base/BaseViewModel.ts +++ b/packages/shared-components/src/viewmodel/BaseViewModel.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type ViewModel } from "../../../packages/shared-components/src/ViewModel"; +import { type ViewModel } from "./ViewModel"; import { Disposables } from "./Disposables"; import { Snapshot } from "./Snapshot"; import { ViewModelSubscriptions } from "./ViewModelSubscriptions"; diff --git a/src/viewmodels/base/Disposables.ts b/packages/shared-components/src/viewmodel/Disposables.ts similarity index 100% rename from src/viewmodels/base/Disposables.ts rename to packages/shared-components/src/viewmodel/Disposables.ts diff --git a/packages/shared-components/src/MockViewModel.ts b/packages/shared-components/src/viewmodel/MockViewModel.ts similarity index 100% rename from packages/shared-components/src/MockViewModel.ts rename to packages/shared-components/src/viewmodel/MockViewModel.ts diff --git a/src/viewmodels/base/Snapshot.ts b/packages/shared-components/src/viewmodel/Snapshot.ts similarity index 100% rename from src/viewmodels/base/Snapshot.ts rename to packages/shared-components/src/viewmodel/Snapshot.ts diff --git a/packages/shared-components/src/ViewModel.ts b/packages/shared-components/src/viewmodel/ViewModel.ts similarity index 100% rename from packages/shared-components/src/ViewModel.ts rename to packages/shared-components/src/viewmodel/ViewModel.ts diff --git a/src/viewmodels/base/ViewModelSubscriptions.ts b/packages/shared-components/src/viewmodel/ViewModelSubscriptions.ts similarity index 100% rename from src/viewmodels/base/ViewModelSubscriptions.ts rename to packages/shared-components/src/viewmodel/ViewModelSubscriptions.ts diff --git a/packages/shared-components/src/viewmodel/index.ts b/packages/shared-components/src/viewmodel/index.ts new file mode 100644 index 0000000000..3699f8dc3f --- /dev/null +++ b/packages/shared-components/src/viewmodel/index.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export * from "./BaseViewModel"; +export * from "./Disposables"; +export * from "./Snapshot"; +export * from "./ViewModelSubscriptions"; +export type * from "./ViewModel"; +export * from "./MockViewModel"; diff --git a/test/viewmodels/base/Disposables-test.ts b/packages/shared-components/src/viewmodel/tests/Disposables.test.ts similarity index 95% rename from test/viewmodels/base/Disposables-test.ts rename to packages/shared-components/src/viewmodel/tests/Disposables.test.ts index 577374a644..5b71f1871d 100644 --- a/test/viewmodels/base/Disposables-test.ts +++ b/packages/shared-components/src/viewmodel/tests/Disposables.test.ts @@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details. import { EventEmitter } from "events"; -import { Disposables } from "../../../src/viewmodels/base/Disposables"; +import { Disposables } from ".."; describe("Disposable", () => { it("isDisposed is true after dispose() is called", () => { diff --git a/test/viewmodels/base/Snapshot-test.ts b/packages/shared-components/src/viewmodel/tests/Snapshot.test.ts similarity index 95% rename from test/viewmodels/base/Snapshot-test.ts rename to packages/shared-components/src/viewmodel/tests/Snapshot.test.ts index 796caa65ab..82cacfc02e 100644 --- a/test/viewmodels/base/Snapshot-test.ts +++ b/packages/shared-components/src/viewmodel/tests/Snapshot.test.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { Snapshot } from "../../../src/viewmodels/base/Snapshot"; +import { Snapshot } from ".."; interface TestSnapshot { key1: string; diff --git a/sonar-project.properties b/sonar-project.properties index e4ce1ea56f..205d82fe6c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,7 +5,7 @@ sonar.organization=element-hq #sonar.sourceEncoding=UTF-8 sonar.sources=src,res,packages/shared-components/src -sonar.tests=test,playwright,src +sonar.tests=test,playwright,src,packages sonar.test.inclusions=test/*,playwright/*,src/**/*.test.*,packages/*/src/**/*.test.* sonar.exclusions=__mocks__,docs,element.io,nginx diff --git a/src/viewmodels/audio/AudioPlayerViewModel.ts b/src/viewmodels/audio/AudioPlayerViewModel.ts index 025265b250..9c8d4c4822 100644 --- a/src/viewmodels/audio/AudioPlayerViewModel.ts +++ b/src/viewmodels/audio/AudioPlayerViewModel.ts @@ -17,7 +17,7 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore"; import { percentageOf } from "../../../packages/shared-components/src/utils/numbers"; import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; -import { BaseViewModel } from "../base/BaseViewModel"; +import { BaseViewModel } from "../../../packages/shared-components/src/viewmodel"; /** * The number of seconds to skip when the user presses the left or right arrow keys. diff --git a/src/viewmodels/event-tiles/TextualEventViewModel.ts b/src/viewmodels/event-tiles/TextualEventViewModel.ts index e887da82b0..a561dfa90d 100644 --- a/src/viewmodels/event-tiles/TextualEventViewModel.ts +++ b/src/viewmodels/event-tiles/TextualEventViewModel.ts @@ -11,7 +11,7 @@ import { type EventTileTypeProps } from "../../events/EventTileFactory"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import { textForEvent } from "../../TextForEvent"; import { type TextualEventViewSnapshot } from "../../../packages/shared-components/src/event-tiles/TextualEventView/TextualEventView"; -import { BaseViewModel } from "../base/BaseViewModel"; +import { BaseViewModel } from "../../../packages/shared-components/src/viewmodel"; export class TextualEventViewModel extends BaseViewModel { public constructor(props: EventTileTypeProps) { diff --git a/yarn.lock b/yarn.lock index aea018e1b7..11f7d27fdb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3999,7 +3999,7 @@ resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.40.0.tgz#53c9ca5ea907d91e4515da64f20a82e5586b882c" integrity sha512-8LRFLs5PEKYs4lOL7aJ4lL/hGCrvEvOYkCR3JggXYXDVMtX4LmfdlKYucSAe98pCmqAAbLRvlRcR1bTOYvM8ug== dependencies: - "@vector-im/matrix-wysiwyg-wasm" "link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm" + "@vector-im/matrix-wysiwyg-wasm" "link:../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm" "@vitest/expect@3.2.4": version "3.2.4"