Merge branch 'livekit' into renovate/livekit-client-2.x

This commit is contained in:
Robin
2024-02-21 08:50:01 -05:00
21 changed files with 824 additions and 1885 deletions

View File

@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v4
- name: Log in to container registry
uses: docker/login-action@3d58c274f17dffee475a5520cbe67f0a882c4dbb
uses: docker/login-action@83a00bc1ab5ded6580f31df1c49e6aaa932d840d
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -54,7 +54,7 @@ jobs:
tar --numeric-owner --transform "s/dist/element-call-${TARBALL_VERSION}/" -cvzf element-call-${TARBALL_VERSION}.tar.gz dist
- name: Upload
uses: actions/upload-artifact@4c0ff1c489dca52fedb26375d7d8fe7bd9233f19
uses: actions/upload-artifact@ef09cdac3e2d3e60d8ccadda691f4f1cec5035cb
env:
GITHUB_TOKEN: ${{ github.token }}
with:

View File

@@ -1,11 +1,11 @@
name: Run jest tests
name: Run unit tests
on:
pull_request: {}
push:
branches: [livekit, full-mesh]
jobs:
jest:
name: Run jest tests
vitest:
name: Run vitest tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -16,7 +16,7 @@ jobs:
cache: "yarn"
- name: Install dependencies
run: "yarn install"
- name: Jest
- name: Vitest
run: "yarn run test"
- name: Upload to codecov
uses: codecov/codecov-action@v4

View File

@@ -13,7 +13,8 @@
"lint:types": "tsc",
"i18n": "node_modules/i18next-parser/bin/cli.js",
"i18n:check": "node_modules/i18next-parser/bin/cli.js --fail-on-warnings --fail-on-update",
"test": "jest",
"test": "vitest",
"test:coverage": "vitest run --coverage",
"backend": "docker-compose -f backend-docker-compose.yml up"
},
"dependencies": {
@@ -88,21 +89,19 @@
"@react-spring/rafz": "^9.7.3",
"@react-types/dialog": "^3.5.5",
"@sentry/vite-plugin": "^2.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/content-type": "^1.1.5",
"@types/dom-screen-wake-lock": "^1.0.1",
"@types/dompurify": "^3.0.2",
"@types/grecaptcha": "^3.0.4",
"@types/jest": "^29.5.5",
"@types/node": "^20.0.0",
"@types/react-router-dom": "^5.3.3",
"@types/request": "^2.48.8",
"@types/sdp-transform": "^2.4.5",
"@types/uuid": "9",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"babel-loader": "^9.0.0",
"babel-plugin-transform-vite-meta-env": "^1.0.3",
"eslint": "^8.14.0",
@@ -116,37 +115,14 @@
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-unicorn": "^51.0.0",
"i18next-parser": "^8.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.2.2",
"jest-environment-jsdom": "^29.3.1",
"jest-mock": "^29.5.0",
"jsdom": "^24.0.0",
"prettier": "^3.0.0",
"sass": "^1.42.1",
"typescript": "^5.1.6",
"typescript-eslint-language-service": "^5.0.5",
"vite": "^5.0.0",
"vite-plugin-html-template": "^1.1.0",
"vite-plugin-svgr": "^4.0.0"
},
"jest": {
"testEnvironment": "./test/environment.ts",
"testMatch": [
"<rootDir>/test/**/*-test.[jt]s?(x)"
],
"transformIgnorePatterns": [
"/node_modules/(?!d3)+$",
"/node_modules/(?!internmap)+$"
],
"moduleNameMapper": {
"\\.css$": "identity-obj-proxy",
"\\.svg\\?react$": "<rootDir>/test/mocks/svgr.ts",
"^\\./IndexedDBWorker\\?worker$": "<rootDir>/test/mocks/workerMock.ts",
"^\\./olm$": "<rootDir>/test/mocks/olmMock.ts"
},
"collectCoverage": true,
"coverageReporters": [
"text",
"cobertura"
]
"vite-plugin-svgr": "^4.0.0",
"vitest": "^1.2.2"
}
}

View File

@@ -143,6 +143,7 @@
"unmute_microphone_button_label": "Unmute microphone",
"version": "Version: {{version}}",
"video_tile": {
"change_fit_contain": "Crop to fit",
"exit_full_screen": "Exit full screen",
"full_screen": "Full screen",
"mute_for_me": "Mute for me",

View File

@@ -188,16 +188,8 @@ export async function initClient(
await client.store.startup();
}
if (client.initCrypto) {
await client.initCrypto();
}
await client.startClient({
// dirty hack to reduce chance of gappy syncs
// should be fixed by spotting gaps and backpaginating
initialSyncLimit: 50,
});
await client.initCrypto();
await client.startClient();
await waitForSync(client);
return client;

View File

@@ -41,6 +41,8 @@ export const RoomAuthView: FC = () => {
// @ts-ignore
(e) => {
e.preventDefault();
setLoading(true);
const data = new FormData(e.target);
const dataForDisplayName = data.get("displayName");
const displayName =

View File

@@ -167,6 +167,12 @@ export class UserMediaTileViewModel extends BaseTileViewModel {
*/
public readonly videoEnabled: StateObservable<boolean>;
private readonly _cropVideo = new BehaviorSubject(true);
/**
* Whether the tile video should be contained inside the tile or be cropped to fit.
*/
public readonly cropVideo = state(this._cropVideo);
public constructor(
id: string,
member: RoomMember | undefined,
@@ -205,6 +211,10 @@ export class UserMediaTileViewModel extends BaseTileViewModel {
this._locallyMuted.next(!this._locallyMuted.value);
}
public toggleFitContain(): void {
this._cropVideo.next(!this._cropVideo.value);
}
public setLocalVolume(value: number): void {
this._localVolume.next(value);
}

View File

@@ -73,7 +73,7 @@ borders don't support gradients */
.videoTile video {
inline-size: 100%;
block-size: 100%;
object-fit: cover;
object-fit: contain;
background-color: var(--cpd-color-bg-subtle-primary);
/* This transform is a no-op, but it forces Firefox to use a different
rendering path, one that actually clips the corners of <video> elements into
@@ -89,6 +89,10 @@ borders don't support gradients */
object-fit: contain;
}
.videoTile.cropVideo video {
object-fit: cover;
}
.videoTile.videoMuted video {
display: none;
}

View File

@@ -206,9 +206,16 @@ const UserMediaTile = subscribe<UserMediaTileProps, HTMLDivElement>(
const mirror = useStateObservable(vm.mirror);
const speaking = useStateObservable(vm.speaking);
const locallyMuted = useStateObservable(vm.locallyMuted);
const cropVideo = useStateObservable(vm.cropVideo);
const localVolume = useStateObservable(vm.localVolume);
const onChangeMute = useCallback(() => vm.toggleLocallyMuted(), [vm]);
const onChangeFitContain = useCallback(() => vm.toggleFitContain(), [vm]);
const onSelectMute = useCallback((e: Event) => e.preventDefault(), []);
const onSelectFitContain = useCallback(
(e: Event) => e.preventDefault(),
[],
);
const onChangeLocalVolume = useCallback(
(v: number) => vm.setLocalVolume(v),
[vm],
@@ -225,6 +232,13 @@ const UserMediaTile = subscribe<UserMediaTileProps, HTMLDivElement>(
label={t("common.profile")}
onSelect={onOpenProfile}
/>
<ToggleMenuItem
Icon={ExpandIcon}
label={t("video_tile.change_fit_contain")}
checked={cropVideo}
onChange={onChangeFitContain}
onSelect={onSelectFitContain}
/>
</>
) : (
<>
@@ -235,6 +249,13 @@ const UserMediaTile = subscribe<UserMediaTileProps, HTMLDivElement>(
onChange={onChangeMute}
onSelect={onSelectMute}
/>
<ToggleMenuItem
Icon={ExpandIcon}
label={t("video_tile.change_fit_contain")}
checked={cropVideo}
onChange={onChangeFitContain}
onSelect={onSelectFitContain}
/>
{/* TODO: Figure out how to make this slider keyboard accessible */}
<MenuItem as="div" Icon={VolumeIcon} label={null} onSelect={null}>
<Slider
@@ -257,6 +278,7 @@ const UserMediaTile = subscribe<UserMediaTileProps, HTMLDivElement>(
className={classNames(className, {
[styles.mirror]: mirror,
[styles.speaking]: showSpeakingIndicator && speaking,
[styles.cropVideo]: cropVideo,
})}
style={style}
targetWidth={targetWidth}

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { vi } from "vitest";
import { screen, render } from "@testing-library/react";
import { Toast } from "../src/Toast";
import userEvent from "@testing-library/user-event";
@@ -35,7 +36,7 @@ test("Toast renders", () => {
});
test("Toast dismisses when clicked", async () => {
const onDismiss = jest.fn();
const onDismiss = vi.fn();
render(
<Toast open={true} onDismiss={onDismiss}>
Hello world!
@@ -47,13 +48,13 @@ test("Toast dismisses when clicked", async () => {
test("Toast dismisses itself after the specified timeout", async () => {
withFakeTimers(() => {
const onDismiss = jest.fn();
const onDismiss = vi.fn();
render(
<Toast open={true} onDismiss={onDismiss} autoDismiss={2000}>
Hello world!
</Toast>,
);
jest.advanceTimersByTime(2000);
vi.advanceTimersByTime(2000);
expect(onDismiss).toHaveBeenCalled();
});
});

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from "jest-mock";
import { vi } from "vitest";
import { getRoomIdentifierFromUrl } from "../src/UrlParams";
import { Config } from "../src/config/Config";
@@ -24,11 +24,11 @@ const ROOM_ID = "!d45f138fsd";
const ORIGIN = "https://call.element.io";
const HOMESERVER = "call.ems.host";
jest.mock("../src/config/Config");
vi.mock("../src/config/Config");
describe("UrlParams", () => {
beforeAll(() => {
mocked(Config.defaultServerName).mockReturnValue("call.ems.host");
vi.mocked(Config.defaultServerName).mockReturnValue("call.ems.host");
});
describe("handles URL with /room/", () => {

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Toast renders 1`] = `
<button

View File

@@ -1,18 +0,0 @@
import { TextEncoder } from "util";
import JSDOMEnvironment_, {
TestEnvironment as TestEnvironment_,
} from "jest-environment-jsdom";
import { JestEnvironmentConfig, EnvironmentContext } from "@jest/environment";
// This is a patched version of jsdom that adds TextEncoder, as a workaround for
// https://github.com/jsdom/jsdom/issues/2524
// Once that issue is resolved, this custom environment file can be deleted
export default class JSDOMEnvironment extends JSDOMEnvironment_ {
constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
super(config, context);
this.global.TextEncoder ??= TextEncoder;
}
}
export const TestEnvironment =
TestEnvironment_ === JSDOMEnvironment_ ? JSDOMEnvironment : TestEnvironment_;

View File

@@ -1 +0,0 @@
module.exports = { loadOlm: jest.fn(async () => {}) };

View File

@@ -1,3 +0,0 @@
// Mock file for SVG imports
const ReactComponent = "svg";
export default ReactComponent;

View File

@@ -1 +0,0 @@
module.exports = jest.fn();

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Mocked, mocked } from "jest-mock";
import { vi, Mocked } from "vitest";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { PosthogAnalytics } from "../../src/analytics/PosthogAnalytics";
import { checkForParallelCalls } from "../../src/room/checkForParallelCalls";
@@ -23,10 +23,10 @@ import { withFakeTimers } from "../utils";
const withMockedPosthog = (
continuation: (posthog: Mocked<PosthogAnalytics>) => void,
) => {
const posthog = mocked({
trackEvent: jest.fn(),
const posthog = vi.mocked({
trackEvent: vi.fn(),
} as unknown as PosthogAnalytics);
const instanceSpy = jest
const instanceSpy = vi
.spyOn(PosthogAnalytics, "instance", "get")
.mockReturnValue(posthog);
try {

View File

@@ -13,12 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { vi } from "vitest";
export function withFakeTimers(continuation: () => void): void {
jest.useFakeTimers();
vi.useFakeTimers();
try {
continuation();
} finally {
jest.useRealTimers();
vi.useRealTimers();
}
}

View File

@@ -12,9 +12,23 @@
"experimentalDecorators": true,
"esModuleInterop": true,
"noUnusedLocals": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"declaration": true,
"resolveJsonModule": true,
"paths": {
// These imports within @livekit/components-core and
// @livekit/components-react are broken under the "bundler" module
// resolution mode, so we need to resolve them manually
"livekit-client/dist/src/room/Room": [
"./node_modules/livekit-client/dist/src/room/Room.d.ts"
],
"livekit-client/dist/src/room/participant/Participant": [
"./node_modules/livekit-client/dist/src/room/participant/Participant.d.ts"
],
"livekit-client/dist/src/proto/livekit_models_pb": [
"./node_modules/livekit-client/dist/src/proto/livekit_models_pb.d.ts"
]
},
// TODO: Enable the following options later.
// "forceConsistentCasingInFileNames": true,
@@ -25,13 +39,14 @@
// "noUncheckedIndexedAccess": true,
// "noUnusedParameters": true,
"plugins": [{ "name": "typescript-eslint-language-service" }],
"plugins": [{ "name": "typescript-eslint-language-service" }]
},
"include": [
"./node_modules/matrix-js-sdk/src/@types/*.d.ts",
"./node_modules/vitest/globals.d.ts",
"./src/**/*.ts",
"./src/**/*.tsx",
"./test/**/*.ts",
"./test/**/*.tsx",
],
"./test/**/*.tsx"
]
}

24
vitest.config.js Normal file
View File

@@ -0,0 +1,24 @@
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";
export default defineConfig((configEnv) =>
mergeConfig(
viteConfig(configEnv),
defineConfig({
test: {
globals: true,
environment: "jsdom",
css: {
modules: {
classNameStrategy: "non-scoped",
},
},
include: ["test/**/*-test.[jt]s?(x)"],
coverage: {
reporter: ["text", "html"],
exclude: ["node_modules/"],
},
},
}),
),
);

2508
yarn.lock

File diff suppressed because it is too large Load Diff