diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index ad143ca36..c734a32e5 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -281,3 +281,6 @@ jobs:
- name: Test CC Widgets
run: yarn run test:cc-widgets
+
+ - name: Test Meetings Widget
+ run: yarn run test:meetings-widget
diff --git a/package.json b/package.json
index 78c839418..44a4242cd 100644
--- a/package.json
+++ b/package.json
@@ -51,11 +51,12 @@
"scripts": {
"clean": "yarn workspaces foreach --all --topological --parallel run clean && rm -rf node_modules",
"clean:dist": "yarn workspaces foreach --all --topological --parallel run clean:dist",
- "test:unit": "yarn run test:tooling && yarn run test:cc-widgets",
+ "test:unit": "yarn run test:tooling && yarn run test:cc-widgets && yarn run test:meetings-widget",
"test:e2e": "yarn playwright test",
"test:styles": "yarn workspaces foreach --all --exclude webex-widgets run test:styles",
"test:tooling": "jest --coverage",
"test:cc-widgets": "yarn workspaces foreach --all --exclude webex-widgets --exclude samples-cc-wc-app --exclude samples-cc-react-app run test:unit",
+ "test:meetings-widget": "yarn workspaces foreach --all --verbose --include @webex/widgets run test:unit",
"build:dev": "NODE_ENV=development yarn build",
"build:prod": "NODE_ENV=production yarn build:serial",
"build": "NODE_OPTIONS=--max-old-space-size=4096 yarn workspaces foreach --all --parallel --topological --exclude samples-cc-react-app --exclude samples-cc-wc-app --exclude samples-meeting-app run build:src",
diff --git a/packages/@webex/widgets/jest.config.js b/packages/@webex/widgets/jest.config.js
new file mode 100644
index 000000000..385314bf2
--- /dev/null
+++ b/packages/@webex/widgets/jest.config.js
@@ -0,0 +1,14 @@
+const jestConfig = require('../../../jest.config.js');
+
+jestConfig.rootDir = '../../../';
+jestConfig.testMatch = ['**/@webex/widgets/tests/**/*.test.{js,jsx}'];
+jestConfig.globals = {
+ ...jestConfig.globals,
+ __appVersion__: '1.0.0-test',
+};
+jestConfig.coveragePathIgnorePatterns = [
+ ...(jestConfig.coveragePathIgnorePatterns || []),
+ 'WebexLogo\\.jsx$',
+];
+
+module.exports = jestConfig;
diff --git a/packages/@webex/widgets/package.json b/packages/@webex/widgets/package.json
index cb908d17f..3dec22ace 100644
--- a/packages/@webex/widgets/package.json
+++ b/packages/@webex/widgets/package.json
@@ -17,6 +17,7 @@
"release:debug": "semantic-release --debug",
"release:dry-run": "semantic-release --dry-run",
"start": "npm run demo:serve",
+ "test:unit": "jest --config jest.config.js --coverage",
"test:e2e": "npm run demo:build && wdio wdio.conf.js",
"test:eslint": "echo 'Broken eslint tests'",
"test:eslint:broken": "eslint src/"
@@ -57,6 +58,9 @@
"@momentum-ui/react": "^23.21.4",
"@semantic-release/changelog": "^6.0.0",
"@semantic-release/git": "^10.0.0",
+ "@testing-library/dom": "10.4.0",
+ "@testing-library/jest-dom": "6.6.2",
+ "@testing-library/react": "16.0.1",
"@wdio/cli": "^7.3.1",
"@wdio/jasmine-framework": "^7.4.6",
"@wdio/junit-reporter": "^7.4.2",
diff --git a/packages/@webex/widgets/tests/WebexMeetings/WebexMeetings.test.jsx b/packages/@webex/widgets/tests/WebexMeetings/WebexMeetings.test.jsx
new file mode 100644
index 000000000..a592a9949
--- /dev/null
+++ b/packages/@webex/widgets/tests/WebexMeetings/WebexMeetings.test.jsx
@@ -0,0 +1,582 @@
+import React, {Component} from 'react';
+import {render, fireEvent, act} from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+let capturedAdapterFactory;
+
+jest.mock('@webex/components', () => ({
+ WebexMediaAccess: (props) => (
+
+ ),
+ WebexMeeting: (props) => (
+
+ ),
+ withAdapter: (WrappedComponent, factory) => {
+ capturedAdapterFactory = factory;
+ return WrappedComponent;
+ },
+ withMeeting: (WrappedComponent) => WrappedComponent,
+}));
+
+jest.mock('@webex/components/dist/css/webex-components.css', () => {});
+
+jest.mock('webex', () => jest.fn((config) => ({__mockWebex: true, ...config})));
+jest.mock('@webex/sdk-component-adapter', () => jest.fn((webex) => ({__mockAdapter: true, webex})));
+
+const Webex = require('webex');
+const WebexSDKAdapter = require('@webex/sdk-component-adapter');
+
+const WebexMeetingsWidget = require('../../src/widgets/WebexMeetings/WebexMeetings').default;
+const adapterFactory = capturedAdapterFactory;
+
+class TestErrorBoundary extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {hasError: false};
+ }
+
+ static getDerivedStateFromError() {
+ return {hasError: true};
+ }
+
+ componentDidCatch(error) {
+ if (this.props.onError) {
+ this.props.onError(error);
+ }
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return null;
+ }
+ return this.props.children;
+ }
+}
+
+const baseMeeting = {
+ ID: 'meeting-123',
+ localAudio: {permission: 'GRANTED'},
+ localVideo: {permission: 'GRANTED'},
+};
+
+const baseProps = {
+ accessToken: 'test-token',
+ meetingDestination: 'test@webex.com',
+ meeting: baseMeeting,
+};
+
+describe('WebexMeetingsWidget', () => {
+ beforeEach(() => {
+ capturedAdapterFactory = undefined;
+ jest.clearAllMocks();
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ jest.useRealTimers();
+ });
+
+ describe('Rendering', () => {
+ it('renders wrapper div with class "webex-meetings-widget" and tabIndex 0', () => {
+ const {container} = render();
+
+ const wrapper = container.firstChild;
+ expect(wrapper).toHaveClass('webex-meetings-widget');
+ expect(wrapper).toHaveAttribute('tabindex', '0');
+ });
+
+ it('renders WebexMediaAccess for microphone when audioPermission is ASKING', () => {
+ const meeting = {...baseMeeting, localAudio: {permission: 'ASKING'}};
+ const {getByTestId, queryByTestId} = render(
+
+ );
+
+ expect(getByTestId('webex-media-access')).toBeInTheDocument();
+ expect(getByTestId('webex-media-access')).toHaveAttribute('data-media', 'microphone');
+ expect(queryByTestId('webex-meeting')).not.toBeInTheDocument();
+ });
+
+ it('renders WebexMediaAccess for camera when videoPermission is ASKING', () => {
+ const meeting = {...baseMeeting, localVideo: {permission: 'ASKING'}};
+ const {getByTestId, queryByTestId} = render(
+
+ );
+
+ expect(getByTestId('webex-media-access')).toBeInTheDocument();
+ expect(getByTestId('webex-media-access')).toHaveAttribute('data-media', 'camera');
+ expect(queryByTestId('webex-meeting')).not.toBeInTheDocument();
+ });
+
+ it('audio ASKING takes priority over video ASKING', () => {
+ const meeting = {
+ ...baseMeeting,
+ localAudio: {permission: 'ASKING'},
+ localVideo: {permission: 'ASKING'},
+ };
+ const {getByTestId} = render();
+
+ expect(getByTestId('webex-media-access')).toHaveAttribute('data-media', 'microphone');
+ });
+
+ it('passes correct meetingID to WebexMediaAccess (microphone case)', () => {
+ const meeting = {...baseMeeting, localAudio: {permission: 'ASKING'}};
+ const {getByTestId} = render();
+
+ expect(getByTestId('webex-media-access')).toHaveAttribute('data-meeting-id', 'meeting-123');
+ });
+
+ it('passes correct meetingID to WebexMediaAccess (camera case)', () => {
+ const meeting = {...baseMeeting, localVideo: {permission: 'ASKING'}};
+ const {getByTestId} = render();
+
+ expect(getByTestId('webex-media-access')).toHaveAttribute('data-meeting-id', 'meeting-123');
+ });
+
+ it('renders WebexMeeting when no permission is ASKING', () => {
+ const {getByTestId, queryByTestId} = render();
+
+ expect(getByTestId('webex-meeting')).toBeInTheDocument();
+ expect(queryByTestId('webex-media-access')).not.toBeInTheDocument();
+ });
+
+ it('passes correct props to WebexMeeting', () => {
+ const controlsFn = jest.fn();
+ const props = {
+ ...baseProps,
+ meetingPasswordOrPin: 'secret123',
+ participantName: 'Test User',
+ layout: 'Focus',
+ controls: controlsFn,
+ controlsCollapseRangeStart: 1,
+ controlsCollapseRangeEnd: -1,
+ };
+ const {getByTestId} = render();
+
+ const meetingEl = getByTestId('webex-meeting');
+ expect(meetingEl).toHaveAttribute('data-meeting-id', 'meeting-123');
+ expect(meetingEl).toHaveAttribute('data-password', 'secret123');
+ expect(meetingEl).toHaveAttribute('data-participant', 'Test User');
+ expect(meetingEl).toHaveAttribute('data-layout', 'Focus');
+ expect(meetingEl).toHaveAttribute('data-collapse-start', '1');
+ expect(meetingEl).toHaveAttribute('data-collapse-end', '-1');
+ });
+
+ it('applies custom className to wrapper', () => {
+ const {container} = render();
+
+ expect(container.firstChild).toHaveClass('webex-meetings-widget');
+ expect(container.firstChild).toHaveClass('my-custom');
+ });
+
+ it('applies custom style to wrapper', () => {
+ const customStyle = {backgroundColor: 'red', width: '500px'};
+ const {container} = render();
+
+ expect(container.firstChild).toHaveStyle({backgroundColor: 'red', width: '500px'});
+ });
+ });
+
+ describe('Default Props', () => {
+ it('layout defaults to Grid', () => {
+ const {getByTestId} = render();
+
+ expect(getByTestId('webex-meeting')).toHaveAttribute('data-layout', 'Grid');
+ });
+
+ it('className defaults to empty string', () => {
+ const {container} = render();
+
+ expect(container.firstChild.className).toBe('webex-meetings-widget ');
+ });
+
+ it('meetingPasswordOrPin defaults to empty string', () => {
+ const {getByTestId} = render();
+
+ expect(getByTestId('webex-meeting')).toHaveAttribute('data-password', '');
+ });
+
+ it('participantName defaults to empty string', () => {
+ const {getByTestId} = render();
+
+ expect(getByTestId('webex-meeting')).toHaveAttribute('data-participant', '');
+ });
+ });
+
+ describe('Error Handling', () => {
+ it('should render null when the widget throws due to invalid meeting prop', () => {
+ const onError = jest.fn();
+ const {container} = render(
+
+
+
+ );
+
+ expect(container.firstChild).toBeNull();
+ expect(onError).toHaveBeenCalledWith(expect.any(Error));
+ });
+ });
+
+ describe('Accessibility - Focus Management', () => {
+ it('on widget focus, sets tabIndex on media containers', () => {
+ const {container} = render();
+ const wrapper = container.firstChild;
+
+ const mediaContainer = document.createElement('div');
+ mediaContainer.classList.add('wxc-interstitial-meeting__media-container');
+ wrapper.querySelector('.webex-meetings-widget__content').appendChild(mediaContainer);
+
+ act(() => {
+ fireEvent.focus(wrapper);
+ jest.advanceTimersByTime(0);
+ });
+
+ expect(mediaContainer.tabIndex).toBe(0);
+ });
+
+ it('on widget focus, falls back to focusing join button when no media containers exist', () => {
+ const {container} = render();
+ const wrapper = container.firstChild;
+
+ const joinButton = document.createElement('button');
+ joinButton.setAttribute('aria-label', 'Join meeting');
+ joinButton.focus = jest.fn();
+ wrapper.appendChild(joinButton);
+
+ act(() => {
+ fireEvent.focus(wrapper);
+ jest.advanceTimersByTime(0);
+ });
+
+ expect(joinButton.focus).toHaveBeenCalled();
+ });
+
+ it('Tab on media container focuses join button', () => {
+ const {container} = render();
+ const wrapper = container.firstChild;
+
+ const mediaContainer = document.createElement('div');
+ mediaContainer.classList.add('wxc-in-meeting__media-container');
+ wrapper.querySelector('.webex-meetings-widget__content').appendChild(mediaContainer);
+
+ act(() => {
+ fireEvent.focus(wrapper);
+ jest.advanceTimersByTime(0);
+ });
+
+ const joinButton = document.createElement('button');
+ joinButton.setAttribute('aria-label', 'Join meeting');
+ joinButton.focus = jest.fn();
+ wrapper.appendChild(joinButton);
+
+ const originalActiveElement = Object.getOwnPropertyDescriptor(document, 'activeElement');
+ Object.defineProperty(document, 'activeElement', {
+ value: mediaContainer,
+ writable: true,
+ configurable: true,
+ });
+
+ try {
+ const tabEvent = new KeyboardEvent('keydown', {
+ key: 'Tab',
+ code: 'Tab',
+ bubbles: true,
+ cancelable: true,
+ });
+ Object.defineProperty(tabEvent, 'currentTarget', {value: mediaContainer});
+ mediaContainer.dispatchEvent(tabEvent);
+
+ expect(joinButton.focus).toHaveBeenCalled();
+ } finally {
+ if (originalActiveElement) {
+ Object.defineProperty(document, 'activeElement', originalActiveElement);
+ } else {
+ delete document.activeElement;
+ }
+ }
+ });
+
+ it('Shift+Tab on media container focuses widget container', () => {
+ const {container} = render();
+ const wrapper = container.firstChild;
+
+ const mediaContainer = document.createElement('div');
+ mediaContainer.classList.add('wxc-interstitial-meeting__media-container');
+ wrapper.querySelector('.webex-meetings-widget__content').appendChild(mediaContainer);
+
+ act(() => {
+ fireEvent.focus(wrapper);
+ jest.advanceTimersByTime(0);
+ });
+
+ wrapper.focus = jest.fn();
+
+ const originalActiveElement = Object.getOwnPropertyDescriptor(document, 'activeElement');
+ Object.defineProperty(document, 'activeElement', {
+ value: mediaContainer,
+ writable: true,
+ configurable: true,
+ });
+
+ try {
+ const shiftTabEvent = new KeyboardEvent('keydown', {
+ key: 'Tab',
+ code: 'Tab',
+ shiftKey: true,
+ bubbles: true,
+ cancelable: true,
+ });
+ Object.defineProperty(shiftTabEvent, 'currentTarget', {value: mediaContainer});
+ mediaContainer.dispatchEvent(shiftTabEvent);
+
+ expect(wrapper.focus).toHaveBeenCalled();
+ } finally {
+ if (originalActiveElement) {
+ Object.defineProperty(document, 'activeElement', originalActiveElement);
+ } else {
+ delete document.activeElement;
+ }
+ }
+ });
+
+ it('content div focus polls for inner meeting media container and focuses it', () => {
+ const {container} = render();
+ const wrapper = container.firstChild;
+ const contentDiv = wrapper.querySelector('.webex-meetings-widget__content');
+
+ expect(contentDiv).toBeTruthy();
+
+ const innerMeeting = document.createElement('div');
+ innerMeeting.classList.add('wxc-in-meeting__media-container');
+ innerMeeting.focus = jest.fn();
+
+ contentDiv.dispatchEvent(new Event('focus'));
+
+ contentDiv.appendChild(innerMeeting);
+
+ act(() => {
+ jest.advanceTimersByTime(500);
+ });
+
+ expect(innerMeeting.focus).toHaveBeenCalled();
+ expect(innerMeeting.tabIndex).toBe(0);
+ });
+
+ it('content div focus attaches one-time Tab handler to move focus to first interactive element', () => {
+ const {container} = render();
+ const wrapper = container.firstChild;
+ const contentDiv = wrapper.querySelector('.webex-meetings-widget__content');
+
+ expect(contentDiv).toBeTruthy();
+
+ const innerMeeting = document.createElement('div');
+ innerMeeting.classList.add('wxc-in-meeting__media-container');
+ contentDiv.appendChild(innerMeeting);
+
+ const interactiveBtn = document.createElement('button');
+ interactiveBtn.focus = jest.fn();
+ innerMeeting.appendChild(interactiveBtn);
+
+ contentDiv.dispatchEvent(new Event('focus'));
+
+ act(() => {
+ jest.advanceTimersByTime(0);
+ });
+
+ const tabEvent = new KeyboardEvent('keydown', {
+ key: 'Tab',
+ bubbles: true,
+ cancelable: true,
+ });
+ innerMeeting.dispatchEvent(tabEvent);
+
+ expect(interactiveBtn.focus).toHaveBeenCalled();
+ });
+
+ it('arrow keys cycle through control buttons', () => {
+ const {container} = render();
+ const wrapper = container.firstChild;
+
+ const controlBar = document.createElement('div');
+ controlBar.classList.add('wxc-meeting-control-bar__controls');
+
+ const btn1 = document.createElement('button');
+ btn1.focus = jest.fn();
+ const btn2 = document.createElement('button');
+ btn2.focus = jest.fn();
+ const btn3 = document.createElement('button');
+ btn3.focus = jest.fn();
+
+ controlBar.appendChild(btn1);
+ controlBar.appendChild(btn2);
+ controlBar.appendChild(btn3);
+ wrapper.appendChild(controlBar);
+
+ act(() => {
+ jest.advanceTimersByTime(700);
+ });
+
+ act(() => {
+ btn1.onkeydown({key: 'ArrowRight', preventDefault: jest.fn()});
+ });
+ expect(btn2.focus).toHaveBeenCalled();
+
+ act(() => {
+ btn1.onkeydown({key: 'ArrowLeft', preventDefault: jest.fn()});
+ });
+ expect(btn3.focus).toHaveBeenCalled();
+ });
+
+ it('MutationObserver re-attaches listeners on DOM changes', () => {
+ let observerCallback;
+ const OriginalMutationObserver = window.MutationObserver;
+
+ window.MutationObserver = class MockMutationObserver {
+ constructor(callback) {
+ observerCallback = callback;
+ }
+ observe() {}
+ disconnect() {}
+ };
+
+ try {
+ const {container} = render();
+ const wrapper = container.firstChild;
+
+ const controlBar = document.createElement('div');
+ controlBar.classList.add('wxc-meeting-control-bar__controls');
+
+ const btn1 = document.createElement('button');
+ controlBar.appendChild(btn1);
+ wrapper.appendChild(controlBar);
+
+ act(() => {
+ jest.advanceTimersByTime(700);
+ });
+
+ expect(btn1.onkeydown).toBeTruthy();
+
+ const newBtn = document.createElement('button');
+ newBtn.focus = jest.fn();
+ controlBar.appendChild(newBtn);
+
+ act(() => {
+ observerCallback();
+ });
+
+ expect(newBtn.onkeydown).toBeTruthy();
+ } finally {
+ window.MutationObserver = OriginalMutationObserver;
+ }
+ });
+ });
+
+ describe('Cleanup', () => {
+ it('disconnects MutationObserver on unmount', () => {
+ const disconnectSpy = jest.fn();
+ const OriginalMutationObserver = window.MutationObserver;
+
+ window.MutationObserver = class MockMutationObserver {
+ constructor(callback) {
+ this.callback = callback;
+ }
+ observe() {}
+ disconnect() {
+ disconnectSpy();
+ }
+ };
+
+ try {
+ const {unmount} = render();
+
+ unmount();
+
+ expect(disconnectSpy).toHaveBeenCalled();
+ } finally {
+ window.MutationObserver = OriginalMutationObserver;
+ }
+ });
+ });
+
+ describe('Adapter Factory', () => {
+ it('creates Webex with correct access_token', () => {
+ adapterFactory({accessToken: 'my-token', fedramp: false});
+
+ expect(Webex).toHaveBeenCalledWith(
+ expect.objectContaining({
+ credentials: {access_token: 'my-token'},
+ })
+ );
+ });
+
+ it('passes fedramp config', () => {
+ adapterFactory({accessToken: 'token', fedramp: true});
+
+ expect(Webex).toHaveBeenCalledWith(
+ expect.objectContaining({
+ config: expect.objectContaining({fedramp: true}),
+ })
+ );
+ });
+
+ it('passes meeting experimental config', () => {
+ adapterFactory({accessToken: 'token', fedramp: false});
+
+ expect(Webex).toHaveBeenCalledWith(
+ expect.objectContaining({
+ config: expect.objectContaining({
+ meetings: {
+ experimental: {
+ enableUnifiedMeetings: true,
+ enableAdhocMeetings: true,
+ },
+ },
+ }),
+ })
+ );
+ });
+
+ it('passes appVersion from __appVersion__ global', () => {
+ adapterFactory({accessToken: 'token', fedramp: false});
+
+ expect(Webex).toHaveBeenCalledWith(
+ expect.objectContaining({
+ config: expect.objectContaining({appVersion: '1.0.0-test'}),
+ })
+ );
+ });
+
+ it('creates WebexSDKAdapter from Webex instance', () => {
+ adapterFactory({accessToken: 'token', fedramp: false});
+
+ expect(WebexSDKAdapter).toHaveBeenCalledTimes(1);
+ const webexInstance = Webex.mock.results[Webex.mock.results.length - 1].value;
+ expect(WebexSDKAdapter).toHaveBeenCalledWith(webexInstance);
+ });
+
+ it('uses dev appName when NODE_ENV is not production', () => {
+ adapterFactory({accessToken: 'token', fedramp: false});
+
+ expect(Webex).toHaveBeenCalledWith(
+ expect.objectContaining({
+ config: expect.objectContaining({appName: 'webex-widgets-meetings-dev'}),
+ })
+ );
+ });
+ });
+});
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index f6c2d52f0..cca76508c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12,10 +12,10 @@ __metadata:
languageName: node
linkType: hard
-"@aml-org/amf-antlr-parsers@npm:0.8.28":
- version: 0.8.28
- resolution: "@aml-org/amf-antlr-parsers@npm:0.8.28"
- checksum: 10c0/ef31cfe06b35017d7855eb3eb3d9c64853e36ea7ad0398cb0754c70c48bfa6abd70d5b7906853877e1ab479c4b01fab3804526eebdfb6adf983ceda35426b16e
+"@aml-org/amf-antlr-parsers@npm:0.8.34":
+ version: 0.8.34
+ resolution: "@aml-org/amf-antlr-parsers@npm:0.8.34"
+ checksum: 10c0/0a8fa2f13df8dd027364e27a258ae23fe6592ea8c55cd898424132b663d3b39ab98d1f1e44f63d808b1c5b47f1e9ec55752ac495b9a56159944c506579eef778
languageName: node
linkType: hard
@@ -8085,9 +8085,9 @@ __metadata:
linkType: hard
"@types/lodash@npm:^4.14.182":
- version: 4.17.16
- resolution: "@types/lodash@npm:4.17.16"
- checksum: 10c0/cf017901b8ab1d7aabc86d5189d9288f4f99f19a75caf020c0e2c77b8d4cead4db0d0b842d009b029339f92399f49f34377dd7c2721053388f251778b4c23534
+ version: 4.17.24
+ resolution: "@types/lodash@npm:4.17.24"
+ checksum: 10c0/b72f60d4daacdad1fa643edb3faba204c02a01eb1ac00a83ff73496a6d236fc55e459c06106e8ced42277dba932d087d8fc090f8de4ef590d3f91e6d6f7ce85a
languageName: node
linkType: hard
@@ -9934,15 +9934,15 @@ __metadata:
linkType: hard
"@webex/event-dictionary-ts@npm:^1.0.1930":
- version: 1.0.1947
- resolution: "@webex/event-dictionary-ts@npm:1.0.1947"
+ version: 1.0.2091
+ resolution: "@webex/event-dictionary-ts@npm:1.0.2091"
dependencies:
amf-client-js: "npm:^5.2.6"
json-schema-to-typescript: "npm:^12.0.0"
minimist: "npm:^1.2.8"
shelljs: "npm:^0.8.5"
webapi-parser: "npm:^0.5.0"
- checksum: 10c0/3b563f15ca895134a7ed24707daf1a937d78e237fe272f5f77e937628b6063e273eb2888d4f14d6a0d8460546d94f9da42819e74142e3d4d931ce7186361fc6c
+ checksum: 10c0/20d0983cebc323593d7a15c8dd26cb7c9d373b0d5e810d6dd818cbb891d42fba844c45fe859ceda810c9daf283fffe939fafcfe0b0068563d34c9731bf8e183e
languageName: node
linkType: hard
@@ -12556,7 +12556,7 @@ __metadata:
languageName: node
linkType: hard
-"@webex/ts-sdp@npm:1.8.2":
+"@webex/ts-sdp@npm:1.8.2, @webex/ts-sdp@npm:^1.8.1":
version: 1.8.2
resolution: "@webex/ts-sdp@npm:1.8.2"
checksum: 10c0/d336f6d3599cbee418de6f02621028266a53c07a0a965e82eb18c9e3993ab9d29d569f5398072de43d9448972776734ca76c1ae2f257859a38793e33f8a519aa
@@ -12570,13 +12570,6 @@ __metadata:
languageName: node
linkType: hard
-"@webex/ts-sdp@npm:^1.8.1":
- version: 1.8.1
- resolution: "@webex/ts-sdp@npm:1.8.1"
- checksum: 10c0/9dc7c63d3274cdbf1cf42c17a2d7bc5afef640bf8200e7c812732c9a19f97d3a84df5bfecba9abc349c19c199ede22c9b7d0db32c1cf802af3d5eb56fda3fefa
- languageName: node
- linkType: hard
-
"@webex/web-capabilities@npm:^1.6.1":
version: 1.6.1
resolution: "@webex/web-capabilities@npm:1.6.1"
@@ -12777,6 +12770,9 @@ __metadata:
"@momentum-ui/react": "npm:^23.21.4"
"@semantic-release/changelog": "npm:^6.0.0"
"@semantic-release/git": "npm:^10.0.0"
+ "@testing-library/dom": "npm:10.4.0"
+ "@testing-library/jest-dom": "npm:6.6.2"
+ "@testing-library/react": "npm:16.0.1"
"@wdio/cli": "npm:^7.3.1"
"@wdio/jasmine-framework": "npm:^7.4.6"
"@wdio/junit-reporter": "npm:^7.4.2"
@@ -13207,15 +13203,15 @@ __metadata:
linkType: hard
"amf-client-js@npm:^5.2.6":
- version: 5.7.0
- resolution: "amf-client-js@npm:5.7.0"
+ version: 5.10.0
+ resolution: "amf-client-js@npm:5.10.0"
dependencies:
- "@aml-org/amf-antlr-parsers": "npm:0.8.28"
+ "@aml-org/amf-antlr-parsers": "npm:0.8.34"
ajv: "npm:6.12.6"
avro-js: "npm:1.11.3"
bin:
amf: bin/amf
- checksum: 10c0/0bea2694b22de128d90696115ea2e716179841d5c076eef8e7ca4dba87f6a24090bac2cb8fb702641b3cc0ea445a000cf5f7260d7a3555ae6c6d3d2502f84909
+ checksum: 10c0/50f7b9d546a719df4ddb2ae40dbf3721849c3be9a2544db3bf133c2336cb047a95057bbaf3c89df539d9c6ec0609a4f6ac934cb82e53ca404aa1407f3032a714
languageName: node
linkType: hard
@@ -34489,9 +34485,9 @@ __metadata:
linkType: hard
"underscore@npm:^1.13.2":
- version: 1.13.7
- resolution: "underscore@npm:1.13.7"
- checksum: 10c0/fad2b4aac48847674aaf3c30558f383399d4fdafad6dd02dd60e4e1b8103b52c5a9e5937e0cc05dacfd26d6a0132ed0410ab4258241240757e4a4424507471cd
+ version: 1.13.8
+ resolution: "underscore@npm:1.13.8"
+ checksum: 10c0/6677688daeda30484823e77c0b89ce4dcf29964a77d5a06f37299c007ab4bb1c66a0ff75e0d274620b62a1fe2a6ba29879f8214533ca611d71a1ae504f2bfc9b
languageName: node
linkType: hard