mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-18 18:59:23 +00:00
simplifications docs and tests
This commit is contained in:
@@ -13,7 +13,7 @@ import { Link } from "@vector-im/compound-web";
|
|||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { CallFooter, type FooterSnapshot } from "./CallFooter";
|
import { CallFooter, type FooterSnapshot } from "./CallFooter";
|
||||||
import inCallViewStyles from "../room/InCallView.module.css";
|
import inCallViewStyles from "../room/InCallView.module.css";
|
||||||
import { createMockedViewModel } from "../state/ViewModel";
|
import { createStaticViewModel } from "../state/ViewModel";
|
||||||
import { ReactionsSenderContext } from "../reactions/useReactionsSender";
|
import { ReactionsSenderContext } from "../reactions/useReactionsSender";
|
||||||
import { type ReactionOption } from "../reactions";
|
import { type ReactionOption } from "../reactions";
|
||||||
import { type GridMode } from "../state/CallViewModel/CallViewModel";
|
import { type GridMode } from "../state/CallViewModel/CallViewModel";
|
||||||
@@ -38,7 +38,7 @@ function CallFooterStoryWrapper({
|
|||||||
}: FooterSnapshot & {
|
}: FooterSnapshot & {
|
||||||
children?: false | JSX.Element | JSX.Element[] | undefined;
|
children?: false | JSX.Element | JSX.Element[] | undefined;
|
||||||
}): ReactNode {
|
}): ReactNode {
|
||||||
const vm = createMockedViewModel(vmSnapshot);
|
const vm = createStaticViewModel(vmSnapshot);
|
||||||
return (
|
return (
|
||||||
<div className={inCallViewStyles.inRoom}>
|
<div className={inCallViewStyles.inRoom}>
|
||||||
<ReactionsSenderContext
|
<ReactionsSenderContext
|
||||||
@@ -70,6 +70,7 @@ const fnArgType = {
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
showLogo: false,
|
showLogo: false,
|
||||||
|
showSettingsButton: true,
|
||||||
layoutMode: "grid",
|
layoutMode: "grid",
|
||||||
audioEnabled: true,
|
audioEnabled: true,
|
||||||
videoEnabled: true,
|
videoEnabled: true,
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export interface FooterSnapshot {
|
|||||||
buttonSize: "md" | "lg";
|
buttonSize: "md" | "lg";
|
||||||
showSettingsButton?: boolean;
|
showSettingsButton?: boolean;
|
||||||
showLayoutSwitcher?: boolean;
|
showLayoutSwitcher?: boolean;
|
||||||
showLogoDebugContainer?: boolean;
|
|
||||||
showLogo?: boolean;
|
showLogo?: boolean;
|
||||||
|
|
||||||
layoutMode?: GridMode;
|
layoutMode?: GridMode;
|
||||||
@@ -127,7 +126,6 @@ export const CallFooter: FC<FooterProps> = ({ ref, children, vm }) => {
|
|||||||
videoToggles,
|
videoToggles,
|
||||||
buttonSize,
|
buttonSize,
|
||||||
showSettingsButton,
|
showSettingsButton,
|
||||||
showLogoDebugContainer,
|
|
||||||
showLogo,
|
showLogo,
|
||||||
} = useViewModel(vm);
|
} = useViewModel(vm);
|
||||||
|
|
||||||
@@ -285,10 +283,10 @@ export const CallFooter: FC<FooterProps> = ({ ref, children, vm }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
{showLogoDebugContainer && logoDebugContainer}
|
{(showLogo || debugTileLayout) && logoDebugContainer}
|
||||||
</div>
|
</div>
|
||||||
{!hideControls && <div className={styles.buttons}>{buttons}</div>}
|
{!hideControls && <div className={styles.buttons}>{buttons}</div>}
|
||||||
{setLayoutMode && layoutMode && (
|
{!hideControls && setLayoutMode && layoutMode && (
|
||||||
<Switch<"spotlight", "grid">
|
<Switch<"spotlight", "grid">
|
||||||
name="layoutMode"
|
name="layoutMode"
|
||||||
aria-label={t("layout_switch_label")}
|
aria-label={t("layout_switch_label")}
|
||||||
|
|||||||
@@ -104,30 +104,30 @@ describe("createCallFooterViewModel", () => {
|
|||||||
}
|
}
|
||||||
it("are empty when both the platform is iOS", () => {
|
it("are empty when both the platform is iOS", () => {
|
||||||
checkEmptyFor("ios", gridLayout);
|
checkEmptyFor("ios", gridLayout);
|
||||||
it("are empty when both the layout is pip", () => {
|
});
|
||||||
checkEmptyFor("desktop", pipLayout);
|
it("are empty when both the layout is pip", () => {
|
||||||
});
|
checkEmptyFor("desktop", pipLayout);
|
||||||
|
});
|
||||||
|
|
||||||
it("are populated when the platform is desktop and the layout is not PiP", () => {
|
it("are populated when the platform is desktop and the layout is not PiP", () => {
|
||||||
platformMock.mockReturnValue("desktop");
|
platformMock.mockReturnValue("desktop");
|
||||||
|
|
||||||
const vm = createCallFooterViewModel(
|
const vm = createCallFooterViewModel(
|
||||||
testScope(),
|
testScope(),
|
||||||
buildMinimalCallViewModel(gridLayout),
|
buildMinimalCallViewModel(gridLayout),
|
||||||
mockMuteStates(),
|
mockMuteStates(),
|
||||||
twoMicsAndOneCamMediaDevices,
|
twoMicsAndOneCamMediaDevices,
|
||||||
/* openSettings */ undefined,
|
/* openSettings */ undefined,
|
||||||
/* reactionIdentifier */ undefined,
|
/* reactionIdentifier */ undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(vm.audioOptions$?.value).toEqual([
|
expect(vm.audioOptions$?.value).toEqual([
|
||||||
{ id: "mic1", label: "Microphone 1" },
|
{ id: "mic1", label: "Microphone 1" },
|
||||||
{ id: "mic2", label: "Microphone 2" },
|
{ id: "mic2", label: "Microphone 2" },
|
||||||
]);
|
]);
|
||||||
expect(vm.videoOptions$?.value).toEqual([
|
expect(vm.videoOptions$?.value).toEqual([
|
||||||
{ id: "cam1", label: "Camera 1" },
|
{ id: "cam1", label: "Camera 1" },
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
import { type Behavior, constant } from "../state/Behavior";
|
import { type Behavior, constant } from "../state/Behavior";
|
||||||
import type { ObservableScope } from "../state/ObservableScope";
|
import type { ObservableScope } from "../state/ObservableScope";
|
||||||
import { type MuteStates } from "../state/MuteStates";
|
import { type MuteStates } from "../state/MuteStates";
|
||||||
import { type ViewModel } from "../state/ViewModel";
|
import { createStaticViewModel, type ViewModel } from "../state/ViewModel";
|
||||||
import { getUrlParams, HeaderStyle } from "../UrlParams";
|
import { getUrlParams, HeaderStyle } from "../UrlParams";
|
||||||
import { platform } from "../Platform";
|
import { platform } from "../Platform";
|
||||||
import { type FooterSnapshot } from "./CallFooter";
|
import { type FooterSnapshot } from "./CallFooter";
|
||||||
@@ -173,8 +173,8 @@ export function createCallFooterViewModel(
|
|||||||
reactionIdentifier: string | undefined,
|
reactionIdentifier: string | undefined,
|
||||||
): ViewModel<FooterSnapshot> {
|
): ViewModel<FooterSnapshot> {
|
||||||
const { showControls, header: headerStyle } = getUrlParams();
|
const { showControls, header: headerStyle } = getUrlParams();
|
||||||
|
const showLogo = headerStyle === HeaderStyle.Standard;
|
||||||
|
|
||||||
const hideLogo = headerStyle !== HeaderStyle.Standard;
|
|
||||||
const isPip$ = scope.behavior(
|
const isPip$ = scope.behavior(
|
||||||
callModel.layout$.pipe(map((l) => l.type === "pip")),
|
callModel.layout$.pipe(map((l) => l.type === "pip")),
|
||||||
);
|
);
|
||||||
@@ -206,12 +206,7 @@ export function createCallFooterViewModel(
|
|||||||
showLayoutSwitcher$: scope.behavior(
|
showLayoutSwitcher$: scope.behavior(
|
||||||
isPip$.pipe(map((l) => !isPip$ && showControls)),
|
isPip$.pipe(map((l) => !isPip$ && showControls)),
|
||||||
),
|
),
|
||||||
showLogoDebugContainer$: scope.behavior(
|
showLogo$: scope.behavior(isPip$.pipe(map((isPip) => showLogo && !isPip))),
|
||||||
combineLatest([isPip$, debugTileLayoutSetting.value$]).pipe(
|
|
||||||
map(([isPip, debugTile]) => !isPip || (!hideLogo && !debugTile)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
showLogo$: scope.behavior(isPip$.pipe(map((l) => !hideLogo && !isPip$))),
|
|
||||||
|
|
||||||
layoutMode$: callModel.gridMode$,
|
layoutMode$: callModel.gridMode$,
|
||||||
setLayoutMode$: constant(callModel.setGridMode),
|
setLayoutMode$: constant(callModel.setGridMode),
|
||||||
@@ -272,31 +267,22 @@ export function createLobbyFooterViewModel(
|
|||||||
showLogo: boolean,
|
showLogo: boolean,
|
||||||
): ViewModel<FooterSnapshot> {
|
): ViewModel<FooterSnapshot> {
|
||||||
return {
|
return {
|
||||||
|
...createStaticViewModel({
|
||||||
|
// we can safly skip any props that we do not need.
|
||||||
|
// The view model will then have less keys.
|
||||||
|
// But as soon as we call `useViewModel` and convert back to a snapshot the missing props will
|
||||||
|
// be correcty matching the snapshot type.
|
||||||
|
showLogo,
|
||||||
|
hideControls: false,
|
||||||
|
asOverlay: false,
|
||||||
|
buttonSize: "lg",
|
||||||
|
showLayoutSwitcher: false,
|
||||||
|
openSettings,
|
||||||
|
hangup,
|
||||||
|
debugTileLayout: false,
|
||||||
|
showSettingsButton: openSettings !== undefined,
|
||||||
|
}),
|
||||||
...buildMuteBehaviors(scope, muteStates),
|
...buildMuteBehaviors(scope, muteStates),
|
||||||
...buildDeviceBehaviors(scope, mediaDevices, constant(false)),
|
...buildDeviceBehaviors(scope, mediaDevices, constant(false)),
|
||||||
hideControls$: constant(false),
|
|
||||||
asOverlay$: constant(false),
|
|
||||||
buttonSize$: constant("lg"),
|
|
||||||
showSettingsButton$: constant(openSettings !== undefined),
|
|
||||||
showLayoutSwitcher$: constant(false),
|
|
||||||
showLogoDebugContainer$: constant(showLogo),
|
|
||||||
showLogo$: constant(showLogo),
|
|
||||||
|
|
||||||
layoutMode$: constant(undefined),
|
|
||||||
setLayoutMode$: constant(undefined),
|
|
||||||
|
|
||||||
sharingScreen$: constant(undefined),
|
|
||||||
toggleScreenSharing$: constant(undefined),
|
|
||||||
|
|
||||||
audioOutputSwitcher$: constant(undefined),
|
|
||||||
|
|
||||||
openSettings$: constant(openSettings),
|
|
||||||
hangup$: constant(hangup),
|
|
||||||
|
|
||||||
reactionIdentifier$: constant(undefined),
|
|
||||||
reactionData$: constant(undefined),
|
|
||||||
|
|
||||||
debugTileLayout$: constant(false),
|
|
||||||
tileStoreGeneration$: constant(0),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
afterEach,
|
||||||
beforeEach,
|
beforeEach,
|
||||||
describe,
|
describe,
|
||||||
expect,
|
expect,
|
||||||
@@ -14,10 +15,10 @@ import {
|
|||||||
type MockedFunction,
|
type MockedFunction,
|
||||||
vi,
|
vi,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { render, type RenderResult } from "@testing-library/react";
|
import { act, render, type RenderResult } from "@testing-library/react";
|
||||||
import { type LocalParticipant } from "livekit-client";
|
import { type LocalParticipant } from "livekit-client";
|
||||||
import { BehaviorSubject, of } from "rxjs";
|
import { BehaviorSubject, of } from "rxjs";
|
||||||
import { BrowserRouter, MemoryRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { RoomContext, useLocalParticipant } from "@livekit/components-react";
|
import { RoomContext, useLocalParticipant } from "@livekit/components-react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
@@ -34,7 +35,10 @@ import {
|
|||||||
} from "../utils/test";
|
} from "../utils/test";
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
import { getBasicCallViewModelEnvironment } from "../utils/test-viewmodel";
|
||||||
import { type CallViewModelOptions } from "../state/CallViewModel/CallViewModel";
|
import {
|
||||||
|
type CallViewModel,
|
||||||
|
type CallViewModelOptions,
|
||||||
|
} from "../state/CallViewModel/CallViewModel";
|
||||||
import { alice, local } from "../utils/test-fixtures";
|
import { alice, local } from "../utils/test-fixtures";
|
||||||
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
||||||
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||||
@@ -100,13 +104,12 @@ beforeEach(() => {
|
|||||||
interface CreateInCallViewArgs {
|
interface CreateInCallViewArgs {
|
||||||
mediaDevices?: ECMediaDevices;
|
mediaDevices?: ECMediaDevices;
|
||||||
callViewModelOptions?: Partial<CallViewModelOptions>;
|
callViewModelOptions?: Partial<CallViewModelOptions>;
|
||||||
/** If set, uses a MemoryRouter with this as the initial entry instead of BrowserRouter */
|
|
||||||
initialRoute?: string;
|
|
||||||
/** If true, wraps the rendered tree in an AppBar provider */
|
/** If true, wraps the rendered tree in an AppBar provider */
|
||||||
withAppBar?: boolean;
|
withAppBar?: boolean;
|
||||||
}
|
}
|
||||||
function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
||||||
rtcSession: MockRTCSession;
|
rtcSession: MockRTCSession;
|
||||||
|
vm: CallViewModel;
|
||||||
} {
|
} {
|
||||||
const mediaDevices = args.mediaDevices ?? mockMediaDevices({});
|
const mediaDevices = args.mediaDevices ?? mockMediaDevices({});
|
||||||
const muteState = mockMuteStates();
|
const muteState = mockMuteStates();
|
||||||
@@ -129,14 +132,6 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
|||||||
const room = rtcSession.room;
|
const room = rtcSession.room;
|
||||||
const client = room.client;
|
const client = room.client;
|
||||||
|
|
||||||
const Router = args.initialRoute
|
|
||||||
? ({ children }: { children: React.ReactNode }): React.ReactNode => (
|
|
||||||
<MemoryRouter initialEntries={[args.initialRoute!]}>
|
|
||||||
{children}
|
|
||||||
</MemoryRouter>
|
|
||||||
)
|
|
||||||
: BrowserRouter;
|
|
||||||
|
|
||||||
const inCallView = (
|
const inCallView = (
|
||||||
<InCallView
|
<InCallView
|
||||||
client={client}
|
client={client}
|
||||||
@@ -163,7 +158,7 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
|||||||
const content = args.withAppBar ? <AppBar>{inCallView}</AppBar> : inCallView;
|
const content = args.withAppBar ? <AppBar>{inCallView}</AppBar> : inCallView;
|
||||||
|
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<Router>
|
<BrowserRouter>
|
||||||
<MediaDevicesContext value={mediaDevices}>
|
<MediaDevicesContext value={mediaDevices}>
|
||||||
<ReactionsSenderProvider
|
<ReactionsSenderProvider
|
||||||
vm={vm}
|
vm={vm}
|
||||||
@@ -174,11 +169,12 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</ReactionsSenderProvider>
|
</ReactionsSenderProvider>
|
||||||
</MediaDevicesContext>
|
</MediaDevicesContext>
|
||||||
</Router>,
|
</BrowserRouter>,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...renderResult,
|
...renderResult,
|
||||||
rtcSession,
|
rtcSession,
|
||||||
|
vm,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,17 +186,37 @@ describe("InCallView", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("settings button with AppBar header", () => {
|
describe("settings button with AppBar header", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// getUrlParams() reads window.location directly rather than from the
|
||||||
|
// React Router context, so MemoryRouter alone is not enough to make
|
||||||
|
// it see "header=app_bar". Push the real URL so both paths agree.
|
||||||
|
window.history.pushState({}, "", "?header=app_bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.history.pushState({}, "", "/");
|
||||||
|
});
|
||||||
|
|
||||||
it("mobile landscape, is accessible when showHeader is false", () => {
|
it("mobile landscape, is accessible when showHeader is false", () => {
|
||||||
// windowSize with height <= 600 results in "flat" windowMode,
|
// windowSize with height <= 600 results in "flat" windowMode,
|
||||||
// which means showHeader$ emits false.
|
// which means showHeader$ emits false.
|
||||||
const { getAllByRole } = createInCallView({
|
const { getAllByRole, queryAllByRole, vm } = createInCallView({
|
||||||
initialRoute: "/?header=app_bar",
|
|
||||||
withAppBar: true,
|
withAppBar: true,
|
||||||
callViewModelOptions: {
|
callViewModelOptions: {
|
||||||
// Set windowMode$ to "flat" (height <= 600)
|
// Set windowMode$ to "flat" (height <= 600)
|
||||||
windowSize$: constant({ width: 1000, height: 500 }),
|
windowSize$: constant({ width: 1000, height: 500 }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// In flat (landscape) mode the footer starts hidden until the user
|
||||||
|
// taps the screen, so no settings button should be accessible yet.
|
||||||
|
expect(queryAllByRole("button", { name: "Settings" })).toHaveLength(0);
|
||||||
|
|
||||||
|
// Simulate a touch tap on the call view to reveal the footer.
|
||||||
|
// (PointerEvent is not available in JSDOM, so we call tapScreen() directly,
|
||||||
|
// which is exactly what the onClick handler does for touch events.)
|
||||||
|
act(() => vm.tapScreen());
|
||||||
|
|
||||||
// When showHeader is false, hideSettingsButton is false,
|
// When showHeader is false, hideSettingsButton is false,
|
||||||
// so the settings button is visible in the footer.
|
// so the settings button is visible in the footer.
|
||||||
const settingsBtn = getAllByRole("button", { name: "Settings" });
|
const settingsBtn = getAllByRole("button", { name: "Settings" });
|
||||||
@@ -220,7 +236,6 @@ describe("InCallView", () => {
|
|||||||
// windowSize with height > 600 and width > 600 results in "normal" windowMode,
|
// windowSize with height > 600 and width > 600 results in "normal" windowMode,
|
||||||
// which means showHeader$ emits true.
|
// which means showHeader$ emits true.
|
||||||
const { getAllByRole } = createInCallView({
|
const { getAllByRole } = createInCallView({
|
||||||
initialRoute: "/?header=app_bar",
|
|
||||||
withAppBar: true,
|
withAppBar: true,
|
||||||
callViewModelOptions: {
|
callViewModelOptions: {
|
||||||
// Set windowMode$ to "normal" (height >= 600)
|
// Set windowMode$ to "normal" (height >= 600)
|
||||||
@@ -228,7 +243,7 @@ describe("InCallView", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// When showHeader is true and headerStyle is AppBar,
|
// When showHeader is true and headerStyle is AppBar,
|
||||||
// hideSettingsButton is true in the footer, but the settings
|
// showSettingsButton is false in the footer, but the settings
|
||||||
// button is rendered in the AppBar via useAppBarSecondaryButton.
|
// button is rendered in the AppBar via useAppBarSecondaryButton.
|
||||||
const settingsBtns = getAllByRole("button", { name: "Settings" });
|
const settingsBtns = getAllByRole("button", { name: "Settings" });
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
|||||||
class="settingsLogoContainer"
|
class="settingsLogoContainer"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-labelledby="_r_8_"
|
aria-labelledby="_r_f_"
|
||||||
class="_icon-button_1215g_8 settingsOnlyShowWide"
|
class="_icon-button_1215g_8 settingsOnlyShowWide"
|
||||||
data-kind="secondary"
|
data-kind="secondary"
|
||||||
data-testid="settings-bottom-left"
|
data-testid="settings-bottom-left"
|
||||||
@@ -304,7 +304,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
|||||||
class="buttons"
|
class="buttons"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-labelledby="_r_d_"
|
aria-labelledby="_r_k_"
|
||||||
class="_button_1nw83_8 settingsOnlyShowNarrow _has-icon_1nw83_60 _icon-only_1nw83_53"
|
class="_button_1nw83_8 settingsOnlyShowNarrow _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||||
data-kind="secondary"
|
data-kind="secondary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@@ -328,7 +328,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
|||||||
<button
|
<button
|
||||||
aria-checked="false"
|
aria-checked="false"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
aria-labelledby="_r_i_"
|
aria-labelledby="_r_p_"
|
||||||
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@@ -352,7 +352,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
|||||||
<button
|
<button
|
||||||
aria-checked="false"
|
aria-checked="false"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
aria-labelledby="_r_n_"
|
aria-labelledby="_r_u_"
|
||||||
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
class="_button_1nw83_8 _has-icon_1nw83_60 _icon-only_1nw83_53"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@@ -374,7 +374,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-labelledby="_r_s_"
|
aria-labelledby="_r_13_"
|
||||||
class="_button_1nw83_8 endCall _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
class="_button_1nw83_8 endCall _has-icon_1nw83_60 _icon-only_1nw83_53 _destructive_1nw83_110"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="lg"
|
||||||
@@ -402,7 +402,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
|||||||
data-size="lg"
|
data-size="lg"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-labelledby="_r_11_"
|
aria-labelledby="_r_18_"
|
||||||
name="layoutMode"
|
name="layoutMode"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="spotlight"
|
value="spotlight"
|
||||||
@@ -420,7 +420,7 @@ exports[`InCallView > rendering > renders 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<input
|
<input
|
||||||
aria-labelledby="_r_16_"
|
aria-labelledby="_r_1d_"
|
||||||
checked=""
|
checked=""
|
||||||
name="layoutMode"
|
name="layoutMode"
|
||||||
type="radio"
|
type="radio"
|
||||||
|
|||||||
@@ -26,7 +26,15 @@ export function useViewModel<Snapshot>(vm: ViewModel<Snapshot>): Snapshot {
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMockedViewModel<Snapshot>(
|
/**
|
||||||
|
* This allows to build a view model (or Partial view model)
|
||||||
|
* with BehaviorSubjects.
|
||||||
|
* It can be used in tests and for simplifying view model creation for non reactive snapshot parameters.
|
||||||
|
*
|
||||||
|
* @param snapshot The snapshot values this view model with start with. ({a: number, b: string})
|
||||||
|
* @returns A view model: ({a$: BehaviroSubject<number>, b$: BehaviroSubject<string>}) (note the automatic addition of $ at the end of the keys)
|
||||||
|
*/
|
||||||
|
export function createStaticViewModel<Snapshot>(
|
||||||
snapshot: Snapshot,
|
snapshot: Snapshot,
|
||||||
): ViewModel<Snapshot> {
|
): ViewModel<Snapshot> {
|
||||||
const vm = {} as ViewModel<Snapshot>;
|
const vm = {} as ViewModel<Snapshot>;
|
||||||
|
|||||||
Reference in New Issue
Block a user