Modernize how we use React contexts (#3359)
* Replace useContext with use The docs recommend the use hook because it is simpler and allows itself to be called conditionally. * Simplify our context providers React 19 lets you omit the '.Provider' bit.
This commit is contained in:
@@ -77,7 +77,7 @@ export const App: FC<Props> = ({ vm }) => {
|
|||||||
{loaded ? (
|
{loaded ? (
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<ClientProvider>
|
<ClientProvider>
|
||||||
<MediaDevicesContext.Provider value={vm.mediaDevices}>
|
<MediaDevicesContext value={vm.mediaDevices}>
|
||||||
<ProcessorProvider>
|
<ProcessorProvider>
|
||||||
<Sentry.ErrorBoundary
|
<Sentry.ErrorBoundary
|
||||||
fallback={(error) => (
|
fallback={(error) => (
|
||||||
@@ -96,7 +96,7 @@ export const App: FC<Props> = ({ vm }) => {
|
|||||||
</Routes>
|
</Routes>
|
||||||
</Sentry.ErrorBoundary>
|
</Sentry.ErrorBoundary>
|
||||||
</ProcessorProvider>
|
</ProcessorProvider>
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</ClientProvider>
|
</ClientProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
createContext,
|
createContext,
|
||||||
useContext,
|
use,
|
||||||
useRef,
|
useRef,
|
||||||
useMemo,
|
useMemo,
|
||||||
type JSX,
|
type JSX,
|
||||||
@@ -69,8 +69,7 @@ const ClientContext = createContext<ClientState | undefined>(undefined);
|
|||||||
|
|
||||||
export const ClientContextProvider = ClientContext.Provider;
|
export const ClientContextProvider = ClientContext.Provider;
|
||||||
|
|
||||||
export const useClientState = (): ClientState | undefined =>
|
export const useClientState = (): ClientState | undefined => use(ClientContext);
|
||||||
useContext(ClientContext);
|
|
||||||
|
|
||||||
export function useClient(): {
|
export function useClient(): {
|
||||||
client?: MatrixClient;
|
client?: MatrixClient;
|
||||||
@@ -350,9 +349,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
return <ErrorPage widget={widget} error={alreadyOpenedErr} />;
|
return <ErrorPage widget={widget} error={alreadyOpenedErr} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <ClientContext value={state}>{children}</ClientContext>;
|
||||||
<ClientContext.Provider value={state}>{children}</ClientContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InitResult = {
|
export type InitResult = {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext, useContext, useMemo } from "react";
|
import { createContext, use, useMemo } from "react";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
|
||||||
import { type MediaDevices } from "./state/MediaDevices";
|
import { type MediaDevices } from "./state/MediaDevices";
|
||||||
@@ -15,7 +15,7 @@ export const MediaDevicesContext = createContext<MediaDevices | undefined>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function useMediaDevices(): MediaDevices {
|
export function useMediaDevices(): MediaDevices {
|
||||||
const mediaDevices = useContext(MediaDevicesContext);
|
const mediaDevices = use(MediaDevicesContext);
|
||||||
if (mediaDevices === undefined)
|
if (mediaDevices === undefined)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"useMediaDevices must be used within a MediaDevices context provider",
|
"useMediaDevices must be used within a MediaDevices context provider",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
createContext,
|
createContext,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
memo,
|
||||||
useContext,
|
use,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
@@ -124,7 +124,7 @@ interface LayoutContext {
|
|||||||
const LayoutContext = createContext<LayoutContext | null>(null);
|
const LayoutContext = createContext<LayoutContext | null>(null);
|
||||||
|
|
||||||
function useLayoutContext(): LayoutContext {
|
function useLayoutContext(): LayoutContext {
|
||||||
const context = useContext(LayoutContext);
|
const context = use(LayoutContext);
|
||||||
if (context === null)
|
if (context === null)
|
||||||
throw new Error("useUpdateLayout called outside a Grid layout context");
|
throw new Error("useUpdateLayout called outside a Grid layout context");
|
||||||
return context;
|
return context;
|
||||||
@@ -532,14 +532,14 @@ export function Grid<
|
|||||||
className={classNames(className, styles.grid)}
|
className={classNames(className, styles.grid)}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<LayoutContext.Provider value={context}>
|
<LayoutContext value={context}>
|
||||||
<LayoutMemo
|
<LayoutMemo
|
||||||
ref={setLayoutRoot}
|
ref={setLayoutRoot}
|
||||||
Layout={Layout}
|
Layout={Layout}
|
||||||
model={model}
|
model={model}
|
||||||
Slot={Slot}
|
Slot={Slot}
|
||||||
/>
|
/>
|
||||||
</LayoutContext.Provider>
|
</LayoutContext>
|
||||||
{tileTransitions((spring, { id, model, onDrag, width, height }) => (
|
{tileTransitions((spring, { id, model, onDrag, width, height }) => (
|
||||||
<TileWrapper
|
<TileWrapper
|
||||||
key={id}
|
key={id}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
createContext,
|
createContext,
|
||||||
type FC,
|
type FC,
|
||||||
type JSX,
|
type JSX,
|
||||||
useContext,
|
use,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
@@ -34,7 +34,7 @@ type ProcessorState = {
|
|||||||
const ProcessorContext = createContext<ProcessorState | undefined>(undefined);
|
const ProcessorContext = createContext<ProcessorState | undefined>(undefined);
|
||||||
|
|
||||||
export function useTrackProcessor(): ProcessorState {
|
export function useTrackProcessor(): ProcessorState {
|
||||||
const state = useContext(ProcessorContext);
|
const state = use(ProcessorContext);
|
||||||
if (state === undefined)
|
if (state === undefined)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"useTrackProcessor must be used within a ProcessorProvider",
|
"useTrackProcessor must be used within a ProcessorProvider",
|
||||||
@@ -83,9 +83,5 @@ export const ProcessorProvider: FC<Props> = ({ children }) => {
|
|||||||
[supported, blurActivated, blur],
|
[supported, blurActivated, blur],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <ProcessorContext value={processorState}>{children}</ProcessorContext>;
|
||||||
<ProcessorContext.Provider value={processorState}>
|
|
||||||
{children}
|
|
||||||
</ProcessorContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { EventType, RelationType } from "matrix-js-sdk";
|
import { EventType, RelationType } from "matrix-js-sdk";
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
useContext,
|
use,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -34,7 +34,7 @@ const ReactionsSenderContext = createContext<
|
|||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
export const useReactionsSender = (): ReactionsSenderContextType => {
|
export const useReactionsSender = (): ReactionsSenderContextType => {
|
||||||
const context = useContext(ReactionsSenderContext);
|
const context = use(ReactionsSenderContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useReactions must be used within a ReactionsProvider");
|
throw new Error("useReactions must be used within a ReactionsProvider");
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ export const ReactionsSenderProvider = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactionsSenderContext.Provider
|
<ReactionsSenderContext
|
||||||
value={{
|
value={{
|
||||||
supportsReactions,
|
supportsReactions,
|
||||||
toggleRaisedHand,
|
toggleRaisedHand,
|
||||||
@@ -165,6 +165,6 @@ export const ReactionsSenderProvider = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ReactionsSenderContext.Provider>
|
</ReactionsSenderContext>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ function createGroupCallView(
|
|||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices({})}>
|
<MediaDevicesContext value={mockMediaDevices({})}>
|
||||||
<ProcessorProvider>
|
<ProcessorProvider>
|
||||||
<GroupCallView
|
<GroupCallView
|
||||||
client={client}
|
client={client}
|
||||||
@@ -164,7 +164,7 @@ function createGroupCallView(
|
|||||||
widget={widget}
|
widget={widget}
|
||||||
/>
|
/>
|
||||||
</ProcessorProvider>
|
</ProcessorProvider>
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</BrowserRouter>,
|
</BrowserRouter>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -149,13 +149,13 @@ function createInCallView(): RenderResult & {
|
|||||||
rtcSession.joined = true;
|
rtcSession.joined = true;
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices({})}>
|
<MediaDevicesContext value={mockMediaDevices({})}>
|
||||||
<ReactionsSenderProvider
|
<ReactionsSenderProvider
|
||||||
vm={vm}
|
vm={vm}
|
||||||
rtcSession={rtcSession as unknown as MatrixRTCSession}
|
rtcSession={rtcSession as unknown as MatrixRTCSession}
|
||||||
>
|
>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<RoomContext.Provider value={livekitRoom}>
|
<RoomContext value={livekitRoom}>
|
||||||
<InCallView
|
<InCallView
|
||||||
client={client}
|
client={client}
|
||||||
hideHeader={true}
|
hideHeader={true}
|
||||||
@@ -182,10 +182,10 @@ function createInCallView(): RenderResult & {
|
|||||||
connState={ConnectionState.Connected}
|
connState={ConnectionState.Connected}
|
||||||
onShareClick={null}
|
onShareClick={null}
|
||||||
/>
|
/>
|
||||||
</RoomContext.Provider>
|
</RoomContext>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</ReactionsSenderProvider>
|
</ReactionsSenderProvider>
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</BrowserRouter>,
|
</BrowserRouter>,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
if (livekitRoom === undefined || vm === null) return null;
|
if (livekitRoom === undefined || vm === null) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomContext.Provider value={livekitRoom}>
|
<RoomContext value={livekitRoom}>
|
||||||
<ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}>
|
<ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}>
|
||||||
<InCallView
|
<InCallView
|
||||||
{...props}
|
{...props}
|
||||||
@@ -181,7 +181,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
connState={connState}
|
connState={connState}
|
||||||
/>
|
/>
|
||||||
</ReactionsSenderProvider>
|
</ReactionsSenderProvider>
|
||||||
</RoomContext.Provider>
|
</RoomContext>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -124,14 +124,14 @@ describe("useMuteStates", () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MediaDevicesContext.Provider
|
<MediaDevicesContext
|
||||||
value={mockMediaDevices({
|
value={mockMediaDevices({
|
||||||
microphone: false,
|
microphone: false,
|
||||||
camera: false,
|
camera: false,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
||||||
@@ -143,9 +143,9 @@ describe("useMuteStates", () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices()}>
|
<MediaDevicesContext value={mockMediaDevices()}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("true");
|
expect(screen.getByTestId("audio-enabled").textContent).toBe("true");
|
||||||
@@ -159,9 +159,9 @@ describe("useMuteStates", () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices()}>
|
<MediaDevicesContext value={mockMediaDevices()}>
|
||||||
<TestComponent isJoined />
|
<TestComponent isJoined />
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
||||||
@@ -178,9 +178,9 @@ describe("useMuteStates", () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices()}>
|
<MediaDevicesContext value={mockMediaDevices()}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
||||||
@@ -192,9 +192,9 @@ describe("useMuteStates", () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter initialEntries={["/room/?skipLobby=true"]}>
|
<MemoryRouter initialEntries={["/room/?skipLobby=true"]}>
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices()}>
|
<MediaDevicesContext value={mockMediaDevices()}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
expect(screen.getByTestId("audio-enabled").textContent).toBe("false");
|
||||||
@@ -224,13 +224,13 @@ describe("useMuteStates", () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MediaDevicesContext.Provider value={devices}>
|
<MediaDevicesContext value={devices}>
|
||||||
<TestComponent />
|
<TestComponent />
|
||||||
<button onClick={onConnectDevicesClick}>Connect devices</button>
|
<button onClick={onConnectDevicesClick}>Connect devices</button>
|
||||||
<button onClick={onDisconnectDevicesClick}>
|
<button onClick={onDisconnectDevicesClick}>
|
||||||
Disconnect devices
|
Disconnect devices
|
||||||
</button>
|
</button>
|
||||||
</MediaDevicesContext.Provider>
|
</MediaDevicesContext>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ afterEach(() => {
|
|||||||
|
|
||||||
test("can play a single sound", async () => {
|
test("can play a single sound", async () => {
|
||||||
const { findByText } = render(
|
const { findByText } = render(
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices({})}>
|
<MediaDevicesContext value={mockMediaDevices({})}>
|
||||||
<TestComponentWrapper />
|
<TestComponentWrapper />
|
||||||
</MediaDevicesContext.Provider>,
|
</MediaDevicesContext>,
|
||||||
);
|
);
|
||||||
await user.click(await findByText("Valid sound"));
|
await user.click(await findByText("Valid sound"));
|
||||||
expect(testAudioContext.createBufferSource).toHaveBeenCalledOnce();
|
expect(testAudioContext.createBufferSource).toHaveBeenCalledOnce();
|
||||||
@@ -115,9 +115,9 @@ test("can play a single sound", async () => {
|
|||||||
|
|
||||||
test("will ignore sounds that are not registered", async () => {
|
test("will ignore sounds that are not registered", async () => {
|
||||||
const { findByText } = render(
|
const { findByText } = render(
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices({})}>
|
<MediaDevicesContext value={mockMediaDevices({})}>
|
||||||
<TestComponentWrapper />
|
<TestComponentWrapper />
|
||||||
</MediaDevicesContext.Provider>,
|
</MediaDevicesContext>,
|
||||||
);
|
);
|
||||||
await user.click(await findByText("Invalid sound"));
|
await user.click(await findByText("Invalid sound"));
|
||||||
expect(testAudioContext.createBufferSource).not.toHaveBeenCalled();
|
expect(testAudioContext.createBufferSource).not.toHaveBeenCalled();
|
||||||
@@ -125,7 +125,7 @@ test("will ignore sounds that are not registered", async () => {
|
|||||||
|
|
||||||
test("will use the correct device", () => {
|
test("will use the correct device", () => {
|
||||||
render(
|
render(
|
||||||
<MediaDevicesContext.Provider
|
<MediaDevicesContext
|
||||||
value={mockMediaDevices({
|
value={mockMediaDevices({
|
||||||
audioOutput: {
|
audioOutput: {
|
||||||
available$: of(new Map<never, never>()),
|
available$: of(new Map<never, never>()),
|
||||||
@@ -135,7 +135,7 @@ test("will use the correct device", () => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<TestComponentWrapper />
|
<TestComponentWrapper />
|
||||||
</MediaDevicesContext.Provider>,
|
</MediaDevicesContext>,
|
||||||
);
|
);
|
||||||
expect(testAudioContext.createBufferSource).not.toHaveBeenCalled();
|
expect(testAudioContext.createBufferSource).not.toHaveBeenCalled();
|
||||||
expect(testAudioContext.setSinkId).toHaveBeenCalledWith("chosen-device");
|
expect(testAudioContext.setSinkId).toHaveBeenCalledWith("chosen-device");
|
||||||
@@ -144,9 +144,9 @@ test("will use the correct device", () => {
|
|||||||
test("will use the correct volume level", async () => {
|
test("will use the correct volume level", async () => {
|
||||||
soundEffectVolumeSetting.setValue(0.33);
|
soundEffectVolumeSetting.setValue(0.33);
|
||||||
const { findByText } = render(
|
const { findByText } = render(
|
||||||
<MediaDevicesContext.Provider value={mockMediaDevices({})}>
|
<MediaDevicesContext value={mockMediaDevices({})}>
|
||||||
<TestComponentWrapper />
|
<TestComponentWrapper />
|
||||||
</MediaDevicesContext.Provider>,
|
</MediaDevicesContext>,
|
||||||
);
|
);
|
||||||
await user.click(await findByText("Valid sound"));
|
await user.click(await findByText("Valid sound"));
|
||||||
expect(testAudioContext.gain.gain.setValueAtTime).toHaveBeenCalledWith(
|
expect(testAudioContext.gain.gain.setValueAtTime).toHaveBeenCalledWith(
|
||||||
@@ -158,7 +158,7 @@ test("will use the correct volume level", async () => {
|
|||||||
|
|
||||||
test("will use the pan if earpiece is selected", async () => {
|
test("will use the pan if earpiece is selected", async () => {
|
||||||
const { findByText } = render(
|
const { findByText } = render(
|
||||||
<MediaDevicesContext.Provider
|
<MediaDevicesContext
|
||||||
value={mockMediaDevices({
|
value={mockMediaDevices({
|
||||||
audioOutput: {
|
audioOutput: {
|
||||||
available$: of(new Map<never, never>()),
|
available$: of(new Map<never, never>()),
|
||||||
@@ -168,7 +168,7 @@ test("will use the pan if earpiece is selected", async () => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<TestComponentWrapper />
|
<TestComponentWrapper />
|
||||||
</MediaDevicesContext.Provider>,
|
</MediaDevicesContext>,
|
||||||
);
|
);
|
||||||
await user.click(await findByText("Valid sound"));
|
await user.click(await findByText("Valid sound"));
|
||||||
expect(testAudioContext.pan.pan.setValueAtTime).toHaveBeenCalledWith(1, 0);
|
expect(testAudioContext.pan.pan.setValueAtTime).toHaveBeenCalledWith(1, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user