From 07ab1080e41a35f6d698b95d06d6f93ca8c41ff1 Mon Sep 17 00:00:00 2001 From: "marc.sirisak" Date: Thu, 1 Aug 2024 12:26:54 +0200 Subject: [PATCH] Squashed 'linked-dependencies/matrix-react-sdk/' changes from e261ae81d87..247296f54c4 247296f54c4 Merge pull request #9 from tchapgouv/upgrade-3.104.0 5de23c023f3 Fix merge conflict v3.104.0 19b59a2f272 Merge tag 'v3.104.0' into upgrade-3.104.0 5dda51f95cf v3.104.0 3e903dad866 Upgrade dependency to matrix-js-sdk@34.2.0 94017ab0ed1 v3.104.0-rc.1 0e2ba42ad70 [Backport staging] Update compound-design-tokens to satisfy compound-web peer dependency (#12820) 63848da2515 [Backport staging] Fix broken jest tests on develop (#12821) 2944632b1aa v3.104.0-rc.0 4eff82fa5e0 Upgrade dependency to matrix-js-sdk@34.2.0-rc.0 be846e65167 Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into staging dafc97fe837 Add release announcement for the new room header (#12802) bb1b7f1fd01 Default the room header to on (#12803) 25fcd6a65fa Update Thread Panel to match latest designs (#12797) 5308c91842a Close any open modals on logout (#12777) 2fd291c23c7 [create-pull-request] automated change (#12804) 844ddc23c03 feat(onboarding): add user guide step 62c56255fc8 [create-pull-request] automated change (#12800) 18398951b4d [create-pull-request] automated change (#12799) 0fc1c53a8e4 Iterate design of right panel empty state (#12796) d202295015f [create-pull-request] automated change (#12795) db4112122a7 [create-pull-request] automated change (#12794) f6f9bb62afe feat(auth): add warning dont close message on check mail f706ac4fa19 Update styling of UserInfo right panel card (#12788) 2920e76b647 Test for lack of WebAssembly support (#12792) 564ea53b839 Fix stray 'account' heading (#12791) 924f5a079f9 Add test for the unsupported browser screen (#12787) 72d32bd6814 [create-pull-request] automated change (#12789) 39d453a5a3e Stop using the js-sdk's compare function (#12782) 3c9bd69d488 Accessibility: Add Landmark navigation (#12190) 4edf4e42cde Remove SpaceScopeHeader (#12785) c57d8463b9d Clear settings store cache on logout (#12786) cf96a6d82c2 [create-pull-request] automated change (#12784) e097cc00d06 Update playwright monorepo to v1.45.2 (#12783) 59d08d84b09 Let Element Call widget receive m.room.create (#12710) c843a4163a4 Let Element Call widget set session memberships (#12713) 831c56d494c v3.103.0 d0a8365be10 Upgrade dependency to matrix-js-sdk@34.1.0 f7a078d250b Update right panel base card styling to match Compound (#12768) c62590c5952 feat(spaces): remove public spaces and subspaces creation 5f10ccb5e4e Update dependency @types/lodash to v4.17.7 (#12747) 13e10654b01 [create-pull-request] automated change (#12779) e6d9eccf1b3 Add test reporter to prevent stale screenshots (#12743) 7863de653af Enable Playwright forbidOnly in CI to catch accidental `test.only` (#12762) 7f959036afb Fix HTML export test (#12778) b4ff7bb3676 feat:(faq): add faq button in space panel b4ef5d3cc3a Fix HTML export missing a bunch of Compound variables (#12774) 38e1da56260 Fix inability to change accent colour consistently in custom theming (#12772) 2e0b5bb4624 Fix edge case of landing on 3pid email link with registration disabled (#12771) 3221f7cadeb Align `widget_build_url_ignore_dm` with call behaviour switch between 1:1 and Widget (#12760) 44454618d80 [create-pull-request] automated change (#12776) 52c32f37c3b Add logging to encryption setup (#12765) 348000100a1 Cleanup tasks in SecurityManager/SetupEncryptionStore (#12764) db95f26ffaf remove "Manually verify all remote sessions" setting (#12706) ba7cf60cd82 Update dependency @vector-im/compound-web to v5.4.0 (#12773) 1082d767d44 Hide tooltips for certain playwright screenshots to avoid flakiness (#12770) 08ee1b8d97e Use multiple workers in Playwright CI to make use of multiple cores (#12769) 0a622f11fca [create-pull-request] automated change (#12767) b53baea1118 [create-pull-request] automated change (#12766) 72e0d100ea7 Update wording shown when keys are withheld (#12761) 2772a9b7677 [create-pull-request] automated change (#12763) 44b98896a79 Move integrations switch (#12733) 19f9f985645 Element-R: Report events with withheld keys separately to Posthog. (#12755) c894bebaa07 Split up slow Playwright tests (#12741) 2712803bbb3 [create-pull-request] automated change (#12759) fff0985bf4a Update dependency @sentry/browser to v8.16.0 (#12757) 454e9346a27 Update all non-major dependencies (#12756) 93d5d2b39ba Update dependency @vector-im/compound-design-tokens to v1.5.0 (#12758) 07f78326e6d Update typescript-eslint monorepo to v7.15.0 (#12752) feb5a491ef2 Update dependency rimraf to v6 (#12754) 7ceb151761e Update dependency @sentry/browser to v8.15.0 (#12749) d053cd26f86 Update dependency @playwright/test to v1.45.1 (#12746) f4aec6a0fe1 Update dependency @vector-im/compound-design-tokens to v1.4.0 (#12750) 9be495af4c6 Update all non-major dependencies (#12744) 28916792206 Update dependency typescript to v5.5.3 (#12748) 81f766f293d v3.103.0-rc.1 6e5b6b4f362 Upgrade dependency to matrix-js-sdk@34.1.0-rc.3 c50c32b3b53 v3.103.0-rc.0 ef0a2fb0122 Update package.json a4f82bb2dcc Specify node version 4d6cea0b7a8 Upgrade dependency to matrix-js-sdk@34.1.0-rc.2 8580b136491 Merge remote-tracking branch 'origin/develop' into staging cf8b87fd14c Add tabs to the right panel (#12672) cd39d91c15a Promote new room header from labs to Beta (#12739) 7487b278573 [create-pull-request] automated change (#12740) 8679ace2b28 Fix incoming call toast crash due to audio refactor (#12737) 2bf33355576 v3.102.0 1e060de31a6 Upgrade dependency to matrix-js-sdk@34.0.0 466f37a83d4 Improve new room header accessibility (#12725) b2a89151e67 Remove redundant call to `setCrypto` (#12738) 2a26afe4389 Redesign room search interface (#12677) 596ad38260a [create-pull-request] automated change (#12735) 5bda8d0d5c2 [create-pull-request] automated change (#12734) 81f29d13dcc Move language settings to 'preferences' (#12723) dcf7643d4ae Fix closing all modals (#12728) a7542dc0ac8 Remove dependency on libolm (#12704) 06117695bc7 Fix close button on forgot password flow (#12732) 2f953f1d0fe New layout selector ui in user settings (#12676) 6f5d21fedb5 [create-pull-request] automated change (#12731) 72b97eac0f7 [create-pull-request] automated change (#12730) e288f61f0a9 Prevent Element appearing in system media controls (#10995) c61eca8c246 Don't consider textual characters to be emoji (#12582) 489bc326742 Remove references to some deprecated js-sdk identifiers (#12729) 55fba07ad1e v3.102.0-rc.1 8febc65bb9f Upgrade dependency to matrix-js-sdk@34.0.0-rc.1 de12d69e6be Flaky-test bot: reopen existing issues (#12727) e48110d7c6d Move the account management button (#12663) 1fbc97296c7 Use LTS nodejs in github workflows (#12726) e75dc4e038d [create-pull-request] automated change (#12724) 0434929ee54 Replace deprecated temporal proposal polyfill (#12636) 6b90fe20abb Extract `focus_search` dispatch action into enum (#12721) b0d2010247b Update new room header facepile tooltip (#12699) 353b6c0de96 Disable xvfb as it is not necessary for headless mode (#12720) 2702f8ba8c7 [create-pull-request] automated change (#12722) 53ea045c544 Update @vector-im/compound-web (#12718) 510fb1ba2f2 Disable profile controls if the HS doesn't allow them to be set (#12652) 922676a7cc2 Clear autocomplete input on selection accept (#12709) 7d8623de897 Fix `Match system theme` toggle (#12719) 6ebaf3b46be [create-pull-request] automated change (#12717) ba7ffd68d2d [create-pull-request] automated change (#12715) 9b00af6b56a [create-pull-request] automated change (#12712) f475e1a6daa [create-pull-request] automated change (#12711) 8feaae66fb3 Playwright: use rust crypto for the bot user (#12708) b449dc835b3 [create-pull-request] automated change (#12707) 33a017b5287 New theme ui in user settings (#12576) 8ede89101af Adjust room header hover transition from 300ms to 200ms (#12703) ea0baee1014 Split out email & phone number settings to separate components & move discovery to privacy tab (#12670) 72475240ece Extract SearchScope and SearchInfo into Searching (#12698) 7a81470558d Pass through BASE_URL env on playwright screenshot docker wrapper (#12696) 86a95cfff7c Switch from graphemer to Intl.Segmenter (#12697) 95c8aa3d187 [create-pull-request] automated change (#12702) b78e6f02f72 [create-pull-request] automated change (#12701) d8a671d7535 Update dependency posthog-js to v1.141.3 (#12700) ffbb903eb6f Update all non-major dependencies (#12681) 7d9ff8b3ff9 Update peter-evans/create-pull-request action to v6 (#12694) 070c4763ec1 Update actions/github-script action to v7 (#12691) 59512f98085 Update dependency eslint-plugin-unicorn to v54 (#12693) 894b4e738da Update dependency @types/uuid to v10 (#12692) 4fbfc946db3 Update actions/checkout action to v4 (#12690) 5c50605f527 Update typescript-eslint monorepo to v7.14.1 (#12689) 4bf8766885a Update dependency typescript to v5.5.2 (#12688) 8c3cc6159e6 Update dependency @vector-im/compound-web to v4.10.0 (#12687) 8977a05387b Update matrix-org (#12683) 3aba3f6fab3 Update dependency @types/node to v18.19.39 (#12682) 91779e0351a Update stylelint (#12684) 7da394bf1d1 Update dependency @playwright/test to v1.45.0 (#12685) 4e6674bbfb4 Update dependency @sentry/browser to v8.11.0 (#12686) 32a838496cc Update peter-evans/create-pull-request digest to 4e1beaa (#12680) eb288987541 Reduce number of parallel runners for Playwright (#12695) 6781465103b v3.102.0-rc.0 1906af291df Upgrade dependency to matrix-js-sdk@34.0.0-rc.0 15de0010a1a Remove legacy crypto parameter (#12679) cea0b7c37ec [create-pull-request] automated change (#12678) f59bd6d4030 Update `@vector-im/compound-web` (#12675) efd309f5f91 Disable playwright tests for legacy crypto (#12662) 9c862907f9b Switch to Rust crypto stack for all logins (#12630) 2843545d1e9 [create-pull-request] automated change (#12673) ce7b5387701 [create-pull-request] automated change (#12671) 736b083a790 Deflake verification playwright tests (#12669) 5eb5ea81d5a Remove redundant copy in deactive uia modal (#12668) 4a4c17253e1 Fix high contrast theme in settings (#12649) 87bdc784b8d Playwright: check the welcome page is loaded and works (#12660) 132669fd28d Remove redundant sleep from playwright test (#12667) 2d8c23e806b Hide voip buttons in group rooms in environments with widgets disabled (#12664) 5bcf76c9abb Playwright test: ensure that links are preserved over login (#12657) 0317755e9c7 Add a github action to check the base branch (#12665) e9abc558691 Work around rust crypto bug which causes flakiness (#12656) 1bf430ba230 Fix background on live location sharing footer (#12629) d5e06c95cb8 [create-pull-request] automated change (#12655) ead7303f785 Remove outdated iframe sandbox attribute (#12633) 6c99b912100 Minor tweaks to UserSettings dialog (#12651) 7e7b55efe3f Playwright tests to replace old `loading-test.tsx` (#12654) e49f8e033db Update to compound-web 4.8.0 (#12650) 76844f5973d Hide voice call button when redundant (#12639) 04e1d7f6c04 Update `@vector-im/compound-web` & fix jest snapshot updates (#12637) b2e9de144c0 Update for js-sdk interface change (#12638) 8ace19112d1 Update definitelyTyped (#12647) 36998f10dbc Update typescript-eslint monorepo to v7.13.1 (#12645) 3d8c89020f9 Update dependency js-xxhash to v4 (#12646) c209edc878e Update all non-major dependencies (#12642) d09793547fd Update dependency emojibase-regex to v15.3.2 (#12644) a4af2870b41 Remove unused state fields in qr oidc (#12588) d6f35a53f7c Replace deprecated babel proposal plugins (#12635) 2b4b415eb3a [create-pull-request] automated change (#12641) 9aa34ce391e [create-pull-request] automated change (#12640) f9e2425ac70 Remove stray setState which caused encryption state shields to flicker (#12632) 28dc65508f2 Update dependency @sentry/browser to v8.9.1 (#12597) 1c59fb534c1 Reset matrix-js-sdk back to develop branch 4b76fcce3c6 Resetting package fields for development 085fa1cf72b Merge branch 'master' into develop 042aa17a60c [create-pull-request] automated change (#12631) 1cf3045a5b6 Fix stray background colour on markdown body (#12628) 5c26d580d81 Fix widgets not being cleaned up correctly. (#12616) 28bcbd40569 Playwright Docker image updates (#12626) 5a1b98d8b7c Improve accessibility of the room summary card (#12586) 5212ae8747c [create-pull-request] automated change (#12627) 72a8f8f03b1 Show tooltips on narrow tabbed views (#12624) 650b9cb0cf9 Add in-progress view to display name EditInPlace (#12609) 6fc9d7641c5 Remove room key history sharing (#12618) d6b9e2aa8a6 Fix config override of other settings levels (#12593) 8e200dc4acf Add login page option for SSO redirects (#12622) bd196aee291 [create-pull-request] automated change (#12620) 6e13e6dcb21 [create-pull-request] automated change (#12619) 6a44f5e0878 Use stable endpoints for MSC3916 (#12602) 8c49f3f2ea2 Update gfm.css to github-markdown-css (#12613) 24e3e08391a Cache e2eStatus to avoid concerning unencrypted flicker when changing rooms (#12606) 347229be601 Avoid flakiness from hover styling in screenshot tests (#12615) 2547777de7c Tweak copy for user verification toast (#12605) 8b4e3e66475 Replace setImmediate with setTimeout (#12614) 21ae29c002a Support s tags for strikethrough for Matrix v1.10 (#12604) ccf751d6b8b Update end-to-end-tests.yaml 2ed9b2e95e3 Don't show 'saved' on display name save error (#12600) 7ae50dbce54 [create-pull-request] automated change (#12611) 7802346ebbd Remove stale `#start` route as it hasn't been used in a long time (#12608) 25e4515c3c1 Fix image upload preview size (#12607) 6198ce511f1 [create-pull-request] automated change (#12603) 6fedf89eaae Update dependency @sentry/browser to v8 (#12567) 81cd53a2256 Update all non-major dependencies (#12601) 9c84c3e689f Update dependency fake-indexeddb to v6 (#12568) 05ea9651b7e Update dependency uuid to v10 (#12599) 2282c6c1cc5 Update matrix-org (#12533) c513f724d1c Update dependency @vector-im/compound-design-tokens to v1.3.0 (#12564) 5a1751f3950 Update typescript-eslint monorepo to v7.13.0 (#12598) 9673df67de4 Update dependency @testing-library/jest-dom to v6.4.6 (#12596) git-subtree-dir: linked-dependencies/matrix-react-sdk git-subtree-split: 247296f54c4b1c90ad8bf0f56ad081d22a3245a2 --- .eslintrc.js | 22 +- .github/workflows/end-to-end-tests.yaml | 18 +- .../workflows/playwright-image-updates.yaml | 4 +- .../workflows/pull_request_base_branch.yaml | 16 + .github/workflows/static_analysis.yaml | 5 + .github/workflows/tests.yml | 2 + CHANGELOG.md | 85 + babel.config.js | 6 +- jest.config.ts | 2 +- package.json | 66 +- playwright.config.ts | 17 +- .../@types/playwright-core.d.ts | 17 +- playwright/Dockerfile | 2 +- playwright/docker-entrypoint.sh | 2 +- .../accessibility/keyboard-navigation.spec.ts | 166 ++ .../e2e/app-loading/feature-detection.spec.ts | 42 + .../app-loading/guest-registration.spec.ts | 45 + .../app-loading/stored-credentials.spec.ts | 68 + .../e2e/audio-player/audio-player.spec.ts | 2 +- .../e2e/chat-export/html-export.spec.ts | 133 + playwright/e2e/composer/CIDER.spec.ts | 106 + .../{composer.spec.ts => RTE.spec.ts} | 70 - .../e2e/create-room/create-room.spec.ts | 2 +- playwright/e2e/crypto/crypto.spec.ts | 584 +--- .../decryption-failure-messages.spec.ts | 302 ++ playwright/e2e/crypto/dehydration.spec.ts | 2 +- ...on.spec.ts => device-verification.spec.ts} | 165 +- playwright/e2e/crypto/event-shields.spec.ts | 269 ++ playwright/e2e/crypto/staged-rollout.spec.ts | 290 -- .../e2e/crypto/user-verification.spec.ts | 145 + playwright/e2e/crypto/utils.ts | 65 + .../e2e/file-upload/image-upload.spec.ts | 6 +- .../forgot-password/forgot-password.spec.ts | 77 + .../get-openid-token.spec.ts | 4 +- .../e2e/integration-manager/kick.spec.ts | 10 +- .../integration-manager/read_events.spec.ts | 10 +- .../integration-manager/send_event.spec.ts | 2 +- playwright/e2e/integration-manager/utils.ts | 7 +- playwright/e2e/invite/invite-dialog.spec.ts | 11 +- .../e2e/knock/create-knock-room.spec.ts | 6 +- .../e2e/lazy-loading/lazy-loading.spec.ts | 9 +- playwright/e2e/login/login.spec.ts | 50 +- .../one-to-one-chat/one-to-one-chat.spec.ts | 8 +- playwright/e2e/polls/pollHistory.spec.ts | 9 +- playwright/e2e/presence/presence.spec.ts | 2 +- .../editing-messages-in-threads.spec.ts | 191 ++ .../editing-messages-main-timeline.spec.ts | 180 ++ .../editing-messages-thread-roots.spec.ts | 179 ++ .../read-receipts/editing-messages.spec.ts | 504 ---- .../e2e/read-receipts/high-level.spec.ts | 148 - playwright/e2e/read-receipts/index.ts | 18 +- .../read-receipts/message-ordering.spec.ts | 92 + ...pec.ts => new-messages-in-threads.spec.ts} | 240 -- .../new-messages-main-timeline.spec.ts | 168 ++ .../new-messages-thread-roots.spec.ts | 118 + .../e2e/read-receipts/notifications.spec.ts | 56 + ...s.spec.ts => reactions-in-threads.spec.ts} | 168 -- .../reactions-main-timeline.spec.ts | 99 + .../reactions-thread-roots.spec.ts | 115 + ....spec.ts => redactions-in-threads.spec.ts} | 517 ---- .../redactions-main-timeline.spec.ts | 331 +++ .../redactions-thread-roots.spec.ts | 232 ++ .../e2e/read-receipts/room-list-order.spec.ts | 61 + playwright/e2e/right-panel/file-panel.spec.ts | 2 +- .../right-panel/notification-panel.spec.ts | 2 +- .../e2e/right-panel/right-panel.spec.ts | 18 +- playwright/e2e/right-panel/utils.ts | 2 +- playwright/e2e/room/room-header.spec.ts | 235 +- .../appearance-user-settings-tab.spec.ts | 219 -- .../appearance-user-settings-tab.spec.ts | 63 + .../appearance-user-settings-tab/index.ts | 241 ++ .../message-layout-panel.ts | 66 + .../theme-choice-panel.spec.ts | 89 + .../general-room-settings-tab.spec.ts | 2 +- .../general-user-settings-tab.spec.ts | 41 +- .../preferences-user-settings-tab.spec.ts | 27 +- .../security-user-settings-tab.spec.ts | 33 +- .../spaces/threads-activity-centre/index.ts | 8 +- .../threadsActivityCentre.spec.ts | 9 +- playwright/e2e/spotlight/spotlight.spec.ts | 2 +- playwright/e2e/threads/threads.spec.ts | 11 +- playwright/e2e/timeline/timeline.spec.ts | 36 +- .../user-onboarding-new.spec.ts | 4 +- playwright/e2e/utils.ts | 13 +- playwright/element-web-test.ts | 184 +- playwright/flaky-reporter.ts | 11 +- playwright/pages/ElementAppPage.ts | 9 + playwright/pages/bot.ts | 10 +- playwright/pages/settings.ts | 11 +- playwright/plugins/homeserver/index.ts | 9 + .../plugins/homeserver/synapse/index.ts | 45 +- .../synapse/templates/guest-enabled/README.md | 1 + .../templates/guest-enabled/homeserver.yaml | 105 + .../templates/guest-enabled/log.config | 50 + ...ported-browser-CompatibilityView-linux.png | Bin 0 -> 70539 bytes .../unsupported-browser-linux.png | Bin 0 -> 59582 bytes .../html-export.spec.ts/html-export-linux.png | Bin 0 -> 40619 bytes ...omSummaryCard-with-verified-e2ee-linux.png | Bin 27122 -> 25768 bytes .../message-edit-history-dialog-linux.png | Bin 7854 -> 7313 bytes .../image-upload-preview-linux.png | Bin 68910 -> 70119 bytes .../forgot-password-linux.png | Bin 0 -> 17851 bytes .../forgot-password-verify-email-linux.png | Bin 0 -> 22624 bytes ...nvite-dialog-room-with-user-pill-linux.png | Bin 23503 -> 21347 bytes .../invite-dialog-room-without-user-linux.png | Bin 17170 -> 15554 bytes .../permalink-rendering-linux.png | Bin 64472 -> 58116 bytes ...iew-with-a-poll-on-bubble-layout-linux.png | Bin 24660 -> 22386 bytes ...View-with-a-poll-on-group-layout-linux.png | Bin 24607 -> 22315 bytes .../file-panel.spec.ts/empty-linux.png | Bin 10600 -> 72623 bytes .../empty-linux.png | Bin 10153 -> 71187 bytes .../with-name-and-address-linux.png | Bin 34363 -> 30147 bytes .../encrypted-room-header-linux.png | Bin 5505 -> 0 bytes .../room-header-highlighted-linux.png | Bin 5784 -> 0 bytes .../room-header.spec.ts/room-header-linux.png | Bin 4952 -> 4796 bytes .../room-header-long-name-linux.png | Bin 6826 -> 6672 bytes .../room-header-video-room-linux.png | Bin 6165 -> 4962 bytes ...der-with-apps-button-highlighted-linux.png | Bin 5951 -> 0 bytes ...with-apps-button-not-highlighted-linux.png | Bin 6867 -> 0 bytes .../appearance-tab-linux.png | Bin 55326 -> 0 bytes .../font-slider-11-darwin.png | Bin 2120 -> 0 bytes .../font-slider-11-linux.png | Bin 2032 -> 0 bytes .../font-slider-21-darwin.png | Bin 3840 -> 0 bytes .../font-slider-21-linux.png | Bin 3708 -> 0 bytes .../window-12px-linux.png | Bin 73087 -> 0 bytes .../appearance-tab-linux.png | Bin 0 -> 49243 bytes .../window-12px-linux.png | Bin 0 -> 61745 bytes .../theme-panel-custom-theme-added-linux.png | Bin 0 -> 17130 bytes .../theme-panel-custom-theme-linux.png | Bin 0 -> 14463 bytes .../theme-panel-dark-linux.png | Bin 0 -> 8711 bytes .../theme-panel-light-linux.png | Bin 0 -> 9167 bytes ...theme-panel-match-system-enabled-linux.png | Bin 0 -> 8367 bytes .../general-linux.png | Bin 50505 -> 49237 bytes .../general-smallscreen-linux.png | Bin 27331 -> 27216 bytes ...ab-should-be-rendered-properly-1-linux.png | Bin 65509 -> 55056 bytes ...ly-to-the-location-on-ThreadView-linux.png | Bin 18752 -> 18839 bytes ...sed-gels-and-messages-irc-layout-linux.png | Bin 48225 -> 43755 bytes .../collapsed-gels-bubble-layout-linux.png | Bin 42405 -> 38703 bytes .../configured-room-irc-layout-linux.png | Bin 42468 -> 38723 bytes ...e-inline-start-margin-irc-layout-linux.png | Bin 53422 -> 48482 bytes .../event-tiles-bubble-layout-linux.png | Bin 41508 -> 37989 bytes ...vent-tiles-compact-modern-layout-linux.png | Bin 51573 -> 46905 bytes .../event-tiles-irc-layout-linux.png | Bin 57004 -> 51713 bytes .../event-tiles-modern-layout-linux.png | Bin 41371 -> 37724 bytes ...ded-gels-and-messages-irc-layout-linux.png | Bin 58101 -> 52718 bytes .../expanded-gels-bubble-layout-linux.png | Bin 52164 -> 47381 bytes .../expanded-gels-emote-irc-layout-linux.png | Bin 59574 -> 54078 bytes .../expanded-gels-irc-layout-linux.png | Bin 53422 -> 48482 bytes .../expanded-gels-modern-layout-linux.png | Bin 52816 -> 48058 bytes ...anded-gels-redaction-placeholder-linux.png | Bin 56826 -> 51629 bytes ...event-line-padding-modern-layout-linux.png | Bin 52562 -> 0 bytes ...ent-line-zero-padding-irc-layout-linux.png | Bin 50908 -> 0 bytes .../highlighted-search-results-linux.png | Bin 19786 -> 17048 bytes ...image-in-timeline-default-layout-linux.png | Bin 37770 -> 34402 bytes ...strings-with-reply-bubble-layout-linux.png | Bin 105658 -> 98812 bytes ...ng-strings-with-reply-irc-layout-linux.png | Bin 104672 -> 95292 bytes ...strings-with-reply-modern-layout-linux.png | Bin 113167 -> 103482 bytes .../search-aux-panel-linux.png | Bin 0 -> 5691 bytes .../search-bar-on-timeline-linux.png | Bin 5125 -> 0 bytes ...search-results-with-TextualEvent-linux.png | Bin 23412 -> 19894 bytes ...nder-the-user-view-as-expected-1-linux.png | Bin 17497 -> 0 bytes .../user-view.spec.ts/user-info-linux.png | Bin 17830 -> 17208 bytes playwright/stale-screenshot-reporter.ts | 74 + res/css/_common.pcss | 25 +- res/css/_components.pcss | 6 +- .../views/messages/_MBeaconBody.pcss | 3 +- .../settings/shared/_SettingsSubsection.pcss | 10 + res/css/structures/_FilePanel.pcss | 4 - res/css/structures/_RightPanel.pcss | 27 - .../views/dialogs/_UserSettingsDialog.pcss | 9 + res/css/views/right_panel/_BaseCard.pcss | 59 +- res/css/views/right_panel/_EmptyState.pcss | 45 + .../right_panel/_RightPanelTabs.pcss} | 12 +- .../views/right_panel/_RoomSummaryCard.pcss | 25 +- res/css/views/right_panel/_ThreadPanel.pcss | 84 +- res/css/views/right_panel/_UserInfo.pcss | 83 +- res/css/views/rooms/_EventTile.pcss | 7 +- res/css/views/rooms/_MemberList.pcss | 1 + res/css/views/rooms/_MessageComposer.pcss | 2 +- res/css/views/rooms/_PresenceLabel.pcss | 4 + res/css/views/rooms/_RoomHeader.pcss | 17 +- res/css/views/rooms/_RoomSearchAuxPanel.pcss | 72 + res/css/views/rooms/_SearchBar.pcss | 83 - res/css/views/settings/_LayoutSwitcher.pcss | 127 +- res/css/views/settings/_ThemeChoicePanel.pcss | 84 +- .../views/settings/_UserProfileSettings.pcss | 13 +- .../tabs/user/_GeneralUserSettingsTab.pcss | 5 + .../views/terms/_InlineTermsAgreement.pcss | 1 + res/img/element-icons/thread-summary.svg | 1 - res/themes/dark-custom/css/dark-custom.pcss | 1 + res/themes/dark/css/_dark.pcss | 2 +- res/themes/dark/css/dark.pcss | 1 + res/themes/legacy-dark/css/_legacy-dark.pcss | 2 +- res/themes/legacy-dark/css/legacy-dark.pcss | 1 + .../legacy-light/css/_legacy-light.pcss | 2 +- res/themes/legacy-light/css/legacy-light.pcss | 1 + res/themes/light-custom/css/_custom.pcss | 2 +- res/themes/light-custom/css/light-custom.pcss | 1 + .../css/_light-high-contrast.pcss | 9 +- .../css/light-high-contrast.pcss | 1 + res/themes/light/css/_light.pcss | 2 +- res/themes/light/css/light.pcss | 1 + src/@types/global.d.ts | 8 + src/DecryptionFailureTracker.ts | 6 +- src/HtmlUtils.tsx | 44 +- src/IConfigOptions.ts | 1 + src/Keyboard.ts | 2 + src/LegacyCallHandler.tsx | 155 +- src/Lifecycle.ts | 3 +- src/Linkify.tsx | 2 + src/Markdown.ts | 2 +- src/MatrixClientPeg.ts | 79 +- src/Modal.tsx | 57 +- src/Notifier.ts | 27 +- src/RoomInvite.tsx | 9 +- src/Searching.ts | 46 + src/SecurityManager.ts | 142 +- src/SlashCommands.tsx | 2 +- src/accessibility/KeyboardShortcuts.ts | 23 +- src/accessibility/LandmarkNavigation.ts | 105 + .../security/CreateSecretStorageDialog.tsx | 17 +- .../security/NewRecoveryMethodDialog.tsx | 4 +- src/audio/BackgroundAudio.ts | 74 + .../structures/AutocompleteInput.tsx | 2 + src/components/structures/ContextMenu.tsx | 4 +- src/components/structures/FilePanel.tsx | 11 +- src/components/structures/LeftPanel.tsx | 11 + src/components/structures/LoggedInView.tsx | 21 +- src/components/structures/MatrixChat.tsx | 23 +- .../structures/NotificationPanel.tsx | 11 +- .../structures/PictureInPictureDragger.tsx | 2 +- src/components/structures/RightPanel.tsx | 13 +- src/components/structures/RoomSearchView.tsx | 131 +- src/components/structures/RoomView.tsx | 68 +- src/components/structures/SpaceRoomView.tsx | 6 +- src/components/structures/TabbedView.tsx | 9 +- src/components/structures/ThreadPanel.tsx | 140 +- .../WaitingForThirdPartyRoomView.tsx | 1 - .../structures/auth/SetupEncryptionBody.tsx | 5 +- .../auth/forgot-password/CheckEmail.tsx | 3 + src/components/views/audio_messages/Clock.tsx | 7 +- .../views/audio_messages/PlayPauseButton.tsx | 6 +- src/components/views/auth/LoginWithQR.tsx | 2 - src/components/views/auth/LoginWithQRFlow.tsx | 2 +- .../views/context_menus/KebabContextMenu.tsx | 6 +- .../views/context_menus/SpaceContextMenu.tsx | 6 +- src/components/views/dialogs/BaseDialog.tsx | 4 +- .../views/dialogs/BulkRedactDialog.tsx | 2 +- .../views/dialogs/DeactivateAccountDialog.tsx | 7 +- .../views/dialogs/ForwardDialog.tsx | 2 +- src/components/views/dialogs/InviteDialog.tsx | 20 +- .../views/dialogs/UserSettingsDialog.tsx | 3 +- .../views/dialogs/devtools/ServerInfo.tsx | 2 +- .../security/RestoreKeyBackupDialog.tsx | 7 +- .../dialogs/spotlight/SpotlightDialog.tsx | 2 +- .../views/dialogs/spotlight/TooltipOption.tsx | 16 +- .../views/elements/AccessibleButton.tsx | 2 + .../views/elements/EventTilePreview.tsx | 2 +- src/components/views/elements/FacePile.tsx | 17 +- src/components/views/elements/LearnMore.tsx | 6 +- .../views/elements/SearchWarning.tsx | 5 +- .../views/elements/SettingsFlag.tsx | 7 + .../views/messages/DecryptionFailureBody.tsx | 4 +- .../views/messages/EncryptionEvent.tsx | 13 +- src/components/views/messages/MFileBody.tsx | 2 +- src/components/views/right_panel/BaseCard.tsx | 70 +- .../views/right_panel/EmptyState.tsx | 42 + .../right_panel/LegacyRoomHeaderButtons.tsx | 12 +- .../views/right_panel/RightPanelTabs.tsx | 86 + .../views/right_panel/RoomSummaryCard.tsx | 220 +- src/components/views/right_panel/UserInfo.tsx | 440 +-- .../views/rooms/BasicMessageComposer.tsx | 11 + .../views/rooms/CollapsibleButton.tsx | 6 +- src/components/views/rooms/EventTile.tsx | 5 +- .../views/rooms/LegacyRoomHeader.tsx | 18 +- src/components/views/rooms/MemberList.tsx | 17 +- src/components/views/rooms/MemberTile.tsx | 4 +- src/components/views/rooms/NewRoomIntro.tsx | 2 +- src/components/views/rooms/PresenceLabel.tsx | 10 +- src/components/views/rooms/RoomHeader.tsx | 104 +- .../rooms/RoomHeader/CallGuestLinkButton.tsx | 2 +- src/components/views/rooms/RoomList.tsx | 20 +- src/components/views/rooms/RoomListHeader.tsx | 6 +- .../views/rooms/RoomSearchAuxPanel.tsx | 79 + src/components/views/rooms/RoomSublist.tsx | 8 +- src/components/views/rooms/RoomTile.tsx | 2 +- src/components/views/rooms/SearchBar.tsx | 147 - .../views/rooms/SendMessageComposer.tsx | 2 +- .../views/rooms/SpaceScopeHeader.tsx | 49 - .../views/rooms/ThirdPartyMemberInfo.tsx | 5 +- .../views/rooms/WhoIsTypingTile.tsx | 4 +- .../views/settings/AvatarSetting.tsx | 8 +- .../views/settings/E2eAdvancedPanel.tsx | 42 - .../views/settings/LayoutSwitcher.tsx | 267 +- .../views/settings/PowerLevelSelector.tsx | 16 +- .../views/settings/SetIntegrationManager.tsx | 7 +- .../views/settings/ThemeChoicePanel.tsx | 567 ++-- .../settings/UserPersonalInfoSettings.tsx | 130 + .../views/settings/UserProfileSettings.tsx | 63 +- .../settings/discovery/DiscoverySettings.tsx | 190 ++ .../settings/shared/SettingsSubsection.tsx | 18 +- .../shared/SettingsSubsectionHeading.tsx | 26 +- .../tabs/room/SecurityRoomSettingsTab.tsx | 3 +- .../tabs/user/AppearanceUserSettingsTab.tsx | 49 +- .../tabs/user/GeneralUserSettingsTab.tsx | 429 +-- .../tabs/user/PreferencesUserSettingsTab.tsx | 88 +- .../tabs/user/SecurityUserSettingsTab.tsx | 9 +- .../views/spaces/QuickThemeSwitcher.tsx | 8 +- .../views/spaces/SpaceCreateMenu.tsx | 171 +- src/components/views/spaces/SpacePanel.tsx | 27 +- .../views/toasts/VerificationRequestToast.tsx | 6 +- src/components/views/voip/CallView.tsx | 2 +- .../LegacyCallView/LegacyCallViewButtons.tsx | 6 +- src/createRoom.ts | 3 +- src/customisations/Security.ts | 5 +- src/dispatcher/actions.ts | 5 + src/editor/deserialize.ts | 2 + src/editor/parts.ts | 21 +- src/hooks/room/useRoomCall.ts | 27 +- src/hooks/useSettings.ts | 34 + src/hooks/useTheme.ts | 53 + src/hooks/useTransition.ts | 35 + src/hooks/useUserOnboardingContext.ts | 12 +- src/hooks/useUserOnboardingTasks.ts | 13 + src/hooks/useWindowWidth.ts | 42 + src/i18n/strings/cs.json | 29 +- src/i18n/strings/de_DE.json | 25 +- src/i18n/strings/el.json | 21 +- src/i18n/strings/en_EN.json | 83 +- src/i18n/strings/eo.json | 13 +- src/i18n/strings/es.json | 25 +- src/i18n/strings/et.json | 29 +- src/i18n/strings/fa.json | 12 +- src/i18n/strings/fi.json | 24 +- src/i18n/strings/fr.json | 101 +- src/i18n/strings/gl.json | 25 +- src/i18n/strings/he.json | 17 +- src/i18n/strings/hu.json | 25 +- src/i18n/strings/id.json | 25 +- src/i18n/strings/is.json | 22 +- src/i18n/strings/it.json | 29 +- src/i18n/strings/ja.json | 27 +- src/i18n/strings/lo.json | 22 +- src/i18n/strings/lt.json | 19 +- src/i18n/strings/nl.json | 25 +- src/i18n/strings/pl.json | 152 +- src/i18n/strings/pt_BR.json | 15 +- src/i18n/strings/ru.json | 25 +- src/i18n/strings/sk.json | 25 +- src/i18n/strings/sq.json | 25 +- src/i18n/strings/sv.json | 29 +- src/i18n/strings/uk.json | 25 +- src/i18n/strings/vi.json | 22 +- src/i18n/strings/zh_Hans.json | 25 +- src/i18n/strings/zh_Hant.json | 25 +- src/integrations/IntegrationManagers.ts | 4 +- src/models/Call.ts | 2 +- src/sentry.ts | 16 +- src/settings/Settings.tsx | 162 +- src/settings/SettingsStore.ts | 81 +- .../controllers/OrderedMultiController.ts | 59 - .../PushToMatrixClientController.ts | 37 - .../controllers/RustCryptoSdkController.ts | 49 - src/settings/handlers/LocalEchoWrapper.ts | 5 + src/settings/handlers/SettingsHandler.ts | 5 + src/stores/ActiveWidgetStore.ts | 6 +- src/stores/CallStore.ts | 30 +- src/stores/ReleaseAnnouncementStore.ts | 2 +- src/stores/SetupEncryptionStore.ts | 48 +- src/stores/right-panel/RightPanelStore.ts | 34 +- .../right-panel/RightPanelStoreIPanelState.ts | 2 + .../action-handlers/View3pidInvite.ts | 2 +- src/stores/room-list/RoomListStore.ts | 2 +- src/stores/room-list/algorithms/Algorithm.ts | 10 +- .../tag-sorting/AlphabeticAlgorithm.ts | 4 +- .../room-list/filters/SpaceFilterCondition.ts | 2 +- src/stores/widgets/StopGapWidget.ts | 3 +- src/stores/widgets/StopGapWidgetDriver.ts | 32 +- src/stores/widgets/WidgetLayoutStore.ts | 6 +- src/theme.ts | 8 +- src/toasts/IncomingCallToast.tsx | 45 +- src/utils/FileDownloader.ts | 2 +- src/utils/MultiInviter.ts | 31 +- src/utils/RoomUpgrade.ts | 2 +- src/utils/StorageManager.ts | 75 +- src/utils/crypto/index.ts | 19 + src/utils/dm/createDmLocalRoom.ts | 6 +- src/utils/exportUtils/HtmlExport.tsx | 128 +- src/utils/exportUtils/exportCSS.ts | 94 +- src/utils/exportUtils/exportCustomCSS.css | 5 + src/utils/strings.ts | 8 +- .../models/VoiceBroadcastRecording.ts | 12 +- src/widgets/ManagedHybrid.ts | 42 +- test/DecryptionFailureTracker-test.ts | 59 +- test/DeviceListener-test.ts | 3 +- test/HtmlUtils-test.tsx | 12 + test/LegacyCallHandler-test.ts | 88 +- test/Lifecycle-test.ts | 4 - test/MatrixClientPeg-test.ts | 206 +- test/Modal-test.ts | 57 + test/Notifier-test.ts | 21 + test/SecurityManager-test.ts | 4 +- test/__snapshots__/HtmlUtils-test.tsx.snap | 41 + .../accessibility/LandmarkNavigation-test.tsx | 130 + .../structures/AutocompleteInput-test.tsx | 30 + test/components/structures/FilePanel-test.tsx | 58 + .../structures/LoggedInView-test.tsx | 33 + .../components/structures/MatrixChat-test.tsx | 2 +- .../structures/RoomSearchView-test.tsx | 34 +- test/components/structures/RoomView-test.tsx | 35 +- .../structures/ThreadPanel-test.tsx | 86 +- .../__snapshots__/FilePanel-test.tsx.snap | 75 + .../__snapshots__/MatrixChat-test.tsx.snap | 24 +- .../__snapshots__/RoomView-test.tsx.snap | 561 +++- .../__snapshots__/ThreadPanel-test.tsx.snap | 28 +- .../RoomGeneralContextMenu-test.tsx | 11 +- .../views/dialogs/ForwardDialog-test.tsx | 5 +- .../views/dialogs/InviteDialog-test.tsx | 6 +- .../views/dialogs/UserSettingsDialog-test.tsx | 1 + .../AppDownloadDialog-test.tsx.snap | 48 +- .../ChangelogDialog-test.tsx.snap | 12 +- .../ConfirmUserActionDialog-test.tsx.snap | 12 +- .../DevtoolsDialog-test.tsx.snap | 13 +- .../__snapshots__/ExportDialog-test.tsx.snap | 12 +- .../__snapshots__/LogoutDialog-test.tsx.snap | 38 +- ...nageRestrictedJoinRuleDialog-test.tsx.snap | 24 +- ...lDeviceKeyVerificationDialog-test.tsx.snap | 24 +- .../MessageEditHistoryDialog-test.tsx.snap | 26 +- .../ServerPickerDialog-test.tsx.snap | 12 +- .../CreateKeyBackupDialog-test.tsx.snap | 12 +- .../ExportE2eKeysDialog-test.tsx.snap | 12 +- .../ImportE2eKeysDialog-test.tsx.snap | 12 +- .../views/elements/SearchWarning-test.tsx | 51 + .../views/elements/SyntaxHighlight-test.tsx | 2 +- .../__snapshots__/AppTile-test.tsx.snap | 51 +- .../__snapshots__/SearchWarning-test.tsx.snap | 50 + .../LocationViewDialog-test.tsx.snap | 1 + .../messages/DecryptionFailureBody-test.tsx | 17 +- .../DecryptionFailureBody-test.tsx.snap | 2 +- .../right_panel/PinnedMessagesCard-test.tsx | 5 +- .../views/right_panel/RightPanelTabs-test.tsx | 72 + .../right_panel/RoomSummaryCard-test.tsx | 97 +- .../views/right_panel/UserInfo-test.tsx | 196 +- .../RightPanelTabs-test.tsx.snap | 119 + .../RoomSummaryCard-test.tsx.snap | 1765 ++++++------ .../__snapshots__/UserInfo-test.tsx.snap | 485 +++- .../views/rooms/LegacyRoomHeader-test.tsx | 2 +- .../views/rooms/MemberList-test.tsx | 4 +- .../views/rooms/RoomHeader-test.tsx | 97 +- .../VideoRoomChatButton-test.tsx.snap | 2 +- .../views/rooms/RoomPreviewBar-test.tsx | 13 +- .../views/rooms/RoomSearchAuxPanel-test.tsx | 95 + .../components/views/rooms/SearchBar-test.tsx | 97 - .../__snapshots__/RoomHeader-test.tsx.snap | 22 +- .../ThirdPartyMemberInfo-test.tsx.snap | 4 +- .../views/settings/LayoutSwitcher-test.tsx | 97 + .../settings/SetIntegrationManager-test.tsx | 104 + .../views/settings/ThemeChoicePanel-test.tsx | 172 +- .../settings/UserProfileSettings-test.tsx | 21 +- .../LayoutSwitcher-test.tsx.snap | 426 +++ .../PowerLevelSelector-test.tsx.snap | 4 +- .../SetIntegrationManager-test.tsx.snap | 56 + .../ThemeChoicePanel-test.tsx.snap | 727 ++++- .../LoginWithQRFlow-test.tsx.snap | 276 +- .../discovery/DiscoverySettings-test.tsx | 104 + .../EmailAddresses-test.tsx.snap | 2 +- .../__snapshots__/PhoneNumbers-test.tsx.snap | 2 +- .../SecurityRoomSettingsTab-test.tsx.snap | 24 +- .../tabs/user/GeneralUserSettingsTab-test.tsx | 69 +- .../tabs/user/LabsUserSettingsTab-test.tsx | 105 +- .../user/PreferencesUserSettingsTab-test.tsx | 50 +- .../AppearanceUserSettingsTab-test.tsx.snap | 604 +++- .../GeneralUserSettingsTab-test.tsx.snap | 63 +- .../KeyboardUserSettingsTab-test.tsx.snap | 46 + .../LabsUserSettingsTab-test.tsx.snap | 55 + .../PreferencesUserSettingsTab-test.tsx.snap | 38 + .../SecurityUserSettingsTab-test.tsx.snap | 101 +- .../views/spaces/QuickThemeSwitcher-test.tsx | 17 +- .../AddExistingToSpaceDialog-test.tsx.snap | 12 +- .../__snapshots__/SpacePanel-test.tsx.snap | 2 +- .../ThreadsActivityCentre-test.tsx.snap | 6 +- test/hooks/useWindowWidth-test.ts | 44 + test/integrations/IntegrationManagers-test.ts | 70 + test/settings/SettingsStore-test.ts | 26 + test/setup/mocks.ts | 36 + test/setup/setupManualMocks.ts | 9 +- test/stores/LifecycleStore-test.ts | 7 +- test/stores/ReleaseAnnouncementStore-test.tsx | 17 +- test/stores/WidgetLayoutStore-test.ts | 69 +- .../right-panel/RightPanelStore-test.ts | 18 + .../action-handlers/View3pidInvite-test.ts | 6 +- test/stores/room-list/RoomListStore-test.ts | 2 - .../widgets/StopGapWidgetDriver-test.ts | 3 + test/test-utils/client.ts | 2 +- test/test-utils/test-utils.ts | 2 +- test/test-utils/utilities.ts | 2 +- test/theme-test.ts | 19 +- test/toasts/IncomingCallToast-test.tsx | 23 +- test/utils/StorageManager-test.ts | 99 +- .../__snapshots__/HTMLExport-test.ts.snap | 118 +- test/utils/leave-behaviour-test.ts | 3 +- .../models/VoiceBroadcastRecording-test.ts | 25 +- test/widgets/ManagedHybrid-test.ts | 79 +- tsconfig.json | 4 +- yarn.lock | 2504 ++++++----------- 503 files changed, 15832 insertions(+), 11695 deletions(-) create mode 100644 .github/workflows/pull_request_base_branch.yaml rename res/css/views/rooms/_SpaceScopeHeader.pcss => playwright/@types/playwright-core.d.ts (63%) create mode 100644 playwright/e2e/accessibility/keyboard-navigation.spec.ts create mode 100644 playwright/e2e/app-loading/feature-detection.spec.ts create mode 100644 playwright/e2e/app-loading/guest-registration.spec.ts create mode 100644 playwright/e2e/app-loading/stored-credentials.spec.ts create mode 100644 playwright/e2e/chat-export/html-export.spec.ts create mode 100644 playwright/e2e/composer/CIDER.spec.ts rename playwright/e2e/composer/{composer.spec.ts => RTE.spec.ts} (80%) create mode 100644 playwright/e2e/crypto/decryption-failure-messages.spec.ts rename playwright/e2e/crypto/{verification.spec.ts => device-verification.spec.ts} (64%) create mode 100644 playwright/e2e/crypto/event-shields.spec.ts delete mode 100644 playwright/e2e/crypto/staged-rollout.spec.ts create mode 100644 playwright/e2e/crypto/user-verification.spec.ts create mode 100644 playwright/e2e/forgot-password/forgot-password.spec.ts create mode 100644 playwright/e2e/read-receipts/editing-messages-in-threads.spec.ts create mode 100644 playwright/e2e/read-receipts/editing-messages-main-timeline.spec.ts create mode 100644 playwright/e2e/read-receipts/editing-messages-thread-roots.spec.ts delete mode 100644 playwright/e2e/read-receipts/editing-messages.spec.ts create mode 100644 playwright/e2e/read-receipts/message-ordering.spec.ts rename playwright/e2e/read-receipts/{new-messages.spec.ts => new-messages-in-threads.spec.ts} (55%) create mode 100644 playwright/e2e/read-receipts/new-messages-main-timeline.spec.ts create mode 100644 playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts create mode 100644 playwright/e2e/read-receipts/notifications.spec.ts rename playwright/e2e/read-receipts/{reactions.spec.ts => reactions-in-threads.spec.ts} (56%) create mode 100644 playwright/e2e/read-receipts/reactions-main-timeline.spec.ts create mode 100644 playwright/e2e/read-receipts/reactions-thread-roots.spec.ts rename playwright/e2e/read-receipts/{redactions.spec.ts => redactions-in-threads.spec.ts} (52%) create mode 100644 playwright/e2e/read-receipts/redactions-main-timeline.spec.ts create mode 100644 playwright/e2e/read-receipts/redactions-thread-roots.spec.ts create mode 100644 playwright/e2e/read-receipts/room-list-order.spec.ts delete mode 100644 playwright/e2e/settings/appearance-user-settings-tab.spec.ts create mode 100644 playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts create mode 100644 playwright/e2e/settings/appearance-user-settings-tab/index.ts create mode 100644 playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.ts create mode 100644 playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts create mode 100644 playwright/plugins/homeserver/synapse/templates/guest-enabled/README.md create mode 100644 playwright/plugins/homeserver/synapse/templates/guest-enabled/homeserver.yaml create mode 100644 playwright/plugins/homeserver/synapse/templates/guest-enabled/log.config create mode 100644 playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-CompatibilityView-linux.png create mode 100644 playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-linux.png create mode 100644 playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png create mode 100644 playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png create mode 100644 playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-verify-email-linux.png delete mode 100644 playwright/snapshots/room/room-header.spec.ts/encrypted-room-header-linux.png delete mode 100644 playwright/snapshots/room/room-header.spec.ts/room-header-highlighted-linux.png delete mode 100644 playwright/snapshots/room/room-header.spec.ts/room-header-with-apps-button-highlighted-linux.png delete mode 100644 playwright/snapshots/room/room-header.spec.ts/room-header-with-apps-button-not-highlighted-linux.png delete mode 100644 playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png delete mode 100644 playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-darwin.png delete mode 100644 playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-linux.png delete mode 100644 playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-darwin.png delete mode 100644 playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-linux.png delete mode 100644 playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/window-12px-linux.png create mode 100644 playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png create mode 100644 playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png create mode 100644 playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-added-linux.png create mode 100644 playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-linux.png create mode 100644 playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-dark-linux.png create mode 100644 playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-light-linux.png create mode 100644 playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-match-system-enabled-linux.png delete mode 100644 playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png delete mode 100644 playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png create mode 100644 playwright/snapshots/timeline/timeline.spec.ts/search-aux-panel-linux.png delete mode 100644 playwright/snapshots/timeline/timeline.spec.ts/search-bar-on-timeline-linux.png delete mode 100644 playwright/snapshots/user-view/user-view.spec.ts/UserView-should-render-the-user-view-as-expected-1-linux.png create mode 100644 playwright/stale-screenshot-reporter.ts create mode 100644 res/css/views/right_panel/_EmptyState.pcss rename res/css/{structures/_NotificationPanel.pcss => views/right_panel/_RightPanelTabs.pcss} (75%) create mode 100644 res/css/views/rooms/_RoomSearchAuxPanel.pcss delete mode 100644 res/css/views/rooms/_SearchBar.pcss delete mode 100644 res/img/element-icons/thread-summary.svg create mode 100644 src/accessibility/LandmarkNavigation.ts create mode 100644 src/audio/BackgroundAudio.ts create mode 100644 src/components/views/right_panel/EmptyState.tsx create mode 100644 src/components/views/right_panel/RightPanelTabs.tsx create mode 100644 src/components/views/rooms/RoomSearchAuxPanel.tsx delete mode 100644 src/components/views/rooms/SearchBar.tsx delete mode 100644 src/components/views/rooms/SpaceScopeHeader.tsx delete mode 100644 src/components/views/settings/E2eAdvancedPanel.tsx create mode 100644 src/components/views/settings/UserPersonalInfoSettings.tsx create mode 100644 src/components/views/settings/discovery/DiscoverySettings.tsx create mode 100644 src/hooks/useTheme.ts create mode 100644 src/hooks/useTransition.ts create mode 100644 src/hooks/useWindowWidth.ts delete mode 100644 src/settings/controllers/OrderedMultiController.ts delete mode 100644 src/settings/controllers/PushToMatrixClientController.ts delete mode 100644 src/settings/controllers/RustCryptoSdkController.ts create mode 100644 src/utils/crypto/index.ts create mode 100644 test/Modal-test.ts create mode 100644 test/accessibility/LandmarkNavigation-test.tsx create mode 100644 test/components/structures/FilePanel-test.tsx create mode 100644 test/components/structures/__snapshots__/FilePanel-test.tsx.snap create mode 100644 test/components/views/elements/SearchWarning-test.tsx create mode 100644 test/components/views/elements/__snapshots__/SearchWarning-test.tsx.snap create mode 100644 test/components/views/right_panel/RightPanelTabs-test.tsx create mode 100644 test/components/views/right_panel/__snapshots__/RightPanelTabs-test.tsx.snap create mode 100644 test/components/views/rooms/RoomSearchAuxPanel-test.tsx delete mode 100644 test/components/views/rooms/SearchBar-test.tsx create mode 100644 test/components/views/settings/LayoutSwitcher-test.tsx create mode 100644 test/components/views/settings/SetIntegrationManager-test.tsx create mode 100644 test/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap create mode 100644 test/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap create mode 100644 test/components/views/settings/discovery/DiscoverySettings-test.tsx create mode 100644 test/hooks/useWindowWidth-test.ts create mode 100644 test/integrations/IntegrationManagers-test.ts create mode 100644 test/setup/mocks.ts diff --git a/.eslintrc.js b/.eslintrc.js index 4bec4e832..3eefd2220 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,6 +34,14 @@ module.exports = { ["*.mxcUrlToHttp", "*.getHttpUriForMxc"], "Use Media helper instead to centralise access for customisation.", ), + ...buildRestrictedPropertiesOptions(["window.setImmediate"], "Use setTimeout instead."), + ], + "no-restricted-globals": [ + "error", + { + name: "setImmediate", + message: "Use setTimeout instead.", + }, ], "import/no-duplicates": ["error"], @@ -70,6 +78,11 @@ module.exports = { name: "matrix-react-sdk/", message: "Please use matrix-react-sdk/src/index instead", }, + { + name: "emojibase-regex", + message: + "This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.", + }, ], patterns: [ { @@ -107,13 +120,9 @@ module.exports = { "!matrix-js-sdk/src/extensible_events_v1/InvalidEventError", "!matrix-js-sdk/src/crypto", "!matrix-js-sdk/src/crypto/aes", - "!matrix-js-sdk/src/crypto/olmlib", - "!matrix-js-sdk/src/crypto/crypto", "!matrix-js-sdk/src/crypto/keybackup", - "!matrix-js-sdk/src/crypto/RoomList", "!matrix-js-sdk/src/crypto/deviceinfo", "!matrix-js-sdk/src/crypto/key_passphrase", - "!matrix-js-sdk/src/crypto/CrossSigning", "!matrix-js-sdk/src/crypto/recoverykey", "!matrix-js-sdk/src/crypto/dehydration", "!matrix-js-sdk/src/oidc", @@ -136,6 +145,11 @@ module.exports = { ], message: "Please use matrix-js-sdk/src/matrix instead", }, + { + group: ["emojibase-regex/emoji*"], + message: + "This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.", + }, ], }, ], diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml index b66394825..97d9692a3 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/end-to-end-tests.yaml @@ -56,6 +56,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: "yarn" + node-version: "lts/*" - name: Fetch layered build id: layered_build @@ -103,7 +104,7 @@ jobs: fail-fast: false matrix: # Run multiple instances in parallel to speed up the tests - runner: [1, 2, 3, 4, 5, 6, 7, 8] + runner: [1, 2, 3, 4, 5, 6] steps: - uses: actions/checkout@v4 with: @@ -121,6 +122,7 @@ jobs: with: cache: "yarn" cache-dependency-path: matrix-react-sdk/yarn.lock + node-version: "lts/*" - name: Install dependencies working-directory: matrix-react-sdk @@ -145,10 +147,8 @@ jobs: run: yarn playwright install --with-deps - name: Run Playwright tests - uses: coactions/setup-xvfb@6b00cf1889f4e1d5a48635647013c0508128ee1a - with: - run: yarn playwright test --shard ${{ matrix.runner }}/${{ strategy.job-total }} - working-directory: matrix-react-sdk + run: yarn playwright test --shard ${{ matrix.runner }}/${{ strategy.job-total }} + working-directory: matrix-react-sdk - name: Upload blob report to GitHub Actions Artifacts if: always() @@ -174,6 +174,7 @@ jobs: if: inputs.skip != true with: cache: "yarn" + node-version: "lts/*" - name: Install dependencies if: inputs.skip != true @@ -189,13 +190,14 @@ jobs: - name: Merge into HTML Report if: inputs.skip != true - run: yarn playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts ./all-blob-reports + run: yarn playwright merge-reports --reporter=html,./playwright/flaky-reporter.ts,./playwright/stale-screenshot-reporter.ts ./all-blob-reports env: # Only pass creds to the flaky-reporter on main branch runs - GITHUB_TOKEN: ${{ github.event.workflow_run.head_branch == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }} + GITHUB_TOKEN: ${{ github.ref_name == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }} + # Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected - name: Upload HTML report - if: inputs.skip != true + if: always() && inputs.skip != true uses: actions/upload-artifact@v4 with: name: html-report diff --git a/.github/workflows/playwright-image-updates.yaml b/.github/workflows/playwright-image-updates.yaml index 15bea28e0..a160b77bc 100644 --- a/.github/workflows/playwright-image-updates.yaml +++ b/.github/workflows/playwright-image-updates.yaml @@ -7,7 +7,7 @@ jobs: update: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Update matrixdotorg/synapse image run: | @@ -20,7 +20,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5 + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 with: token: ${{ secrets.ELEMENT_BOT_TOKEN }} branch: actions/playwright-image-updates diff --git a/.github/workflows/pull_request_base_branch.yaml b/.github/workflows/pull_request_base_branch.yaml new file mode 100644 index 000000000..49d7bcef7 --- /dev/null +++ b/.github/workflows/pull_request_base_branch.yaml @@ -0,0 +1,16 @@ +name: Pull Request Base Branch +on: + pull_request: + types: [opened, edited, synchronize] +jobs: + check_base_branch: + name: Check PR base branch + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + script: | + const baseBranch = context.payload.pull_request.base.ref; + if (!['develop', 'staging'].includes(baseBranch) && !baseBranch.startsWith('feat/')) { + core.setFailed(`Invalid base branch: ${baseBranch}`); + } diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index 6e225467a..94ed2c748 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -25,6 +25,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: "yarn" + node-version: "lts/*" - name: Install Deps run: "./scripts/ci/install-deps.sh" @@ -83,6 +84,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: "yarn" + node-version: "lts/*" # Does not need branch matching as only analyses this layer - name: Install Deps @@ -100,6 +102,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: "yarn" + node-version: "lts/*" # Does not need branch matching as only analyses this layer - name: Install Deps @@ -117,6 +120,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: "yarn" + node-version: "lts/*" # Does not need branch matching as only analyses this layer - name: Install Deps @@ -134,6 +138,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: "yarn" + node-version: "lts/*" - name: Install Deps run: "scripts/ci/layered.sh" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3815c4fb4..e8418a951 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,6 +44,7 @@ jobs: - name: Yarn cache uses: actions/setup-node@v4 with: + node-version: "lts/*" cache: "yarn" - name: Install Deps @@ -115,6 +116,7 @@ jobs: - uses: actions/setup-node@v4 with: cache: "yarn" + node-version: "lts/*" - name: Run tests run: "./scripts/ci/app-tests.sh" diff --git a/CHANGELOG.md b/CHANGELOG.md index ea499883a..280d6dd56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,88 @@ +Changes in [3.104.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.104.0) (2024-07-30) +======================================================================================================= +## ✨ Features + +* Add release announcement for the new room header ([#12802](https://github.com/matrix-org/matrix-react-sdk/pull/12802)). Contributed by @MidhunSureshR. +* Default the room header to on ([#12803](https://github.com/matrix-org/matrix-react-sdk/pull/12803)). Contributed by @MidhunSureshR. +* Update Thread Panel to match latest designs ([#12797](https://github.com/matrix-org/matrix-react-sdk/pull/12797)). Contributed by @t3chguy. +* Close any open modals on logout ([#12777](https://github.com/matrix-org/matrix-react-sdk/pull/12777)). Contributed by @dbkr. +* Iterate design of right panel empty state ([#12796](https://github.com/matrix-org/matrix-react-sdk/pull/12796)). Contributed by @t3chguy. +* Update styling of UserInfo right panel card ([#12788](https://github.com/matrix-org/matrix-react-sdk/pull/12788)). Contributed by @t3chguy. +* Accessibility: Add Landmark navigation ([#12190](https://github.com/matrix-org/matrix-react-sdk/pull/12190)). Contributed by @akirk. +* Let Element Call widget receive m.room.create ([#12710](https://github.com/matrix-org/matrix-react-sdk/pull/12710)). Contributed by @AndrewFerr. +* Let Element Call widget set session memberships ([#12713](https://github.com/matrix-org/matrix-react-sdk/pull/12713)). Contributed by @AndrewFerr. +* Update right panel base card styling to match Compound ([#12768](https://github.com/matrix-org/matrix-react-sdk/pull/12768)). Contributed by @t3chguy. +* Align `widget_build_url_ignore_dm` with call behaviour switch between 1:1 and Widget ([#12760](https://github.com/matrix-org/matrix-react-sdk/pull/12760)). Contributed by @t3chguy. +* Move integrations switch ([#12733](https://github.com/matrix-org/matrix-react-sdk/pull/12733)). Contributed by @dbkr. +* Element-R: Report events with withheld keys separately to Posthog. ([#12755](https://github.com/matrix-org/matrix-react-sdk/pull/12755)). Contributed by @richvdh. + +## 🐛 Bug Fixes + +* Test for lack of WebAssembly support ([#12792](https://github.com/matrix-org/matrix-react-sdk/pull/12792)). Contributed by @dbkr. +* Fix stray 'account' heading ([#12791](https://github.com/matrix-org/matrix-react-sdk/pull/12791)). Contributed by @dbkr. +* Add test for the unsupported browser screen ([#12787](https://github.com/matrix-org/matrix-react-sdk/pull/12787)). Contributed by @dbkr. +* Fix HTML export test ([#12778](https://github.com/matrix-org/matrix-react-sdk/pull/12778)). Contributed by @dbkr. +* Fix HTML export missing a bunch of Compound variables ([#12774](https://github.com/matrix-org/matrix-react-sdk/pull/12774)). Contributed by @t3chguy. +* Fix inability to change accent colour consistently in custom theming ([#12772](https://github.com/matrix-org/matrix-react-sdk/pull/12772)). Contributed by @t3chguy. +* Fix edge case of landing on 3pid email link with registration disabled ([#12771](https://github.com/matrix-org/matrix-react-sdk/pull/12771)). Contributed by @t3chguy. + + +Changes in [3.103.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.103.0) (2024-07-16) +======================================================================================================= +## ✨ Features + +* Add tabs to the right panel ([#12672](https://github.com/matrix-org/matrix-react-sdk/pull/12672)). Contributed by @MidhunSureshR. +* Promote new room header from labs to Beta ([#12739](https://github.com/matrix-org/matrix-react-sdk/pull/12739)). Contributed by @t3chguy. +* Redesign room search interface ([#12677](https://github.com/matrix-org/matrix-react-sdk/pull/12677)). Contributed by @t3chguy. +* Move language settings to 'preferences' ([#12723](https://github.com/matrix-org/matrix-react-sdk/pull/12723)). Contributed by @dbkr. +* New layout selector ui in user settings ([#12676](https://github.com/matrix-org/matrix-react-sdk/pull/12676)). Contributed by @florianduros. +* Prevent Element appearing in system media controls ([#10995](https://github.com/matrix-org/matrix-react-sdk/pull/10995)). Contributed by @SuperKenVery. +* Move the account management button ([#12663](https://github.com/matrix-org/matrix-react-sdk/pull/12663)). Contributed by @dbkr. +* Disable profile controls if the HS doesn't allow them to be set ([#12652](https://github.com/matrix-org/matrix-react-sdk/pull/12652)). Contributed by @dbkr. +* New theme ui in user settings ([#12576](https://github.com/matrix-org/matrix-react-sdk/pull/12576)). Contributed by @florianduros. +* Adjust room header hover transition from 300ms to 200ms ([#12703](https://github.com/matrix-org/matrix-react-sdk/pull/12703)). Contributed by @t3chguy. +* Split out email \& phone number settings to separate components \& move discovery to privacy tab ([#12670](https://github.com/matrix-org/matrix-react-sdk/pull/12670)). Contributed by @dbkr. + +## 🐛 Bug Fixes + +* Fix incoming call toast crash due to audio refactor ([#12737](https://github.com/matrix-org/matrix-react-sdk/pull/12737)). Contributed by @t3chguy. +* Improve new room header accessibility ([#12725](https://github.com/matrix-org/matrix-react-sdk/pull/12725)). Contributed by @t3chguy. +* Fix closing all modals ([#12728](https://github.com/matrix-org/matrix-react-sdk/pull/12728)). Contributed by @dbkr. +* Fix close button on forgot password flow ([#12732](https://github.com/matrix-org/matrix-react-sdk/pull/12732)). Contributed by @dbkr. +* Don't consider textual characters to be emoji ([#12582](https://github.com/matrix-org/matrix-react-sdk/pull/12582)). Contributed by @robintown. +* Clear autocomplete input on selection accept ([#12709](https://github.com/matrix-org/matrix-react-sdk/pull/12709)). Contributed by @dbkr. +* Fix `Match system theme` toggle ([#12719](https://github.com/matrix-org/matrix-react-sdk/pull/12719)). Contributed by @florianduros. + + +Changes in [3.102.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.102.0) (2024-07-08) +======================================================================================================= +## ✨ Features + +* Switch to Rust crypto stack for all logins ([#12630](https://github.com/matrix-org/matrix-react-sdk/pull/12630)). Contributed by @richvdh. +* Hide voip buttons in group rooms in environments with widgets disabled ([#12664](https://github.com/matrix-org/matrix-react-sdk/pull/12664)). Contributed by @t3chguy. +* Minor tweaks to UserSettings dialog ([#12651](https://github.com/matrix-org/matrix-react-sdk/pull/12651)). Contributed by @florianduros. +* Hide voice call button when redundant ([#12639](https://github.com/matrix-org/matrix-react-sdk/pull/12639)). Contributed by @t3chguy. +* Improve accessibility of the room summary card ([#12586](https://github.com/matrix-org/matrix-react-sdk/pull/12586)). Contributed by @t3chguy. +* Show tooltips on narrow tabbed views ([#12624](https://github.com/matrix-org/matrix-react-sdk/pull/12624)). Contributed by @dbkr. +* Update gfm.css to github-markdown-css ([#12613](https://github.com/matrix-org/matrix-react-sdk/pull/12613)). Contributed by @t3chguy. +* Cache e2eStatus to avoid concerning unencrypted flicker when changing rooms ([#12606](https://github.com/matrix-org/matrix-react-sdk/pull/12606)). Contributed by @t3chguy. +* Tweak copy for user verification toast ([#12605](https://github.com/matrix-org/matrix-react-sdk/pull/12605)). Contributed by @t3chguy. +* Support s tags for strikethrough for Matrix v1.10 ([#12604](https://github.com/matrix-org/matrix-react-sdk/pull/12604)). Contributed by @t3chguy. + +## 🐛 Bug Fixes + +* Remove redundant copy in deactive uia modal ([#12668](https://github.com/matrix-org/matrix-react-sdk/pull/12668)). Contributed by @t3chguy. +* Fix high contrast theme in settings ([#12649](https://github.com/matrix-org/matrix-react-sdk/pull/12649)). Contributed by @florianduros. +* Fix background on live location sharing footer ([#12629](https://github.com/matrix-org/matrix-react-sdk/pull/12629)). Contributed by @t3chguy. +* Remove outdated iframe sandbox attribute ([#12633](https://github.com/matrix-org/matrix-react-sdk/pull/12633)). Contributed by @t3chguy. +* Remove stray setState which caused encryption state shields to flicker ([#12632](https://github.com/matrix-org/matrix-react-sdk/pull/12632)). Contributed by @t3chguy. +* Fix stray background colour on markdown body ([#12628](https://github.com/matrix-org/matrix-react-sdk/pull/12628)). Contributed by @t3chguy. +* Fix widgets not being cleaned up correctly. ([#12616](https://github.com/matrix-org/matrix-react-sdk/pull/12616)). Contributed by @toger5. +* Add in-progress view to display name EditInPlace ([#12609](https://github.com/matrix-org/matrix-react-sdk/pull/12609)). Contributed by @dbkr. +* Fix config override of other settings levels ([#12593](https://github.com/matrix-org/matrix-react-sdk/pull/12593)). Contributed by @langleyd. +* Don't show 'saved' on display name save error ([#12600](https://github.com/matrix-org/matrix-react-sdk/pull/12600)). Contributed by @dbkr. + + Changes in [3.101.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.101.0) (2024-06-18) ======================================================================================================= ## ✨ Features diff --git a/babel.config.js b/babel.config.js index 1c4adcb7f..a9e4b5c13 100644 --- a/babel.config.js +++ b/babel.config.js @@ -17,9 +17,9 @@ module.exports = { ], plugins: [ "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-numeric-separator", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-transform-numeric-separator", + "@babel/plugin-transform-class-properties", + "@babel/plugin-transform-object-rest-spread", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-transform-runtime", ], diff --git a/jest.config.ts b/jest.config.ts index 182c28f68..7293e5b3b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -22,7 +22,7 @@ const config: Config = { testEnvironment: "jsdom", testMatch: ["/test/**/*-test.[jt]s?(x)"], globalSetup: "/test/globalSetup.ts", - setupFiles: ["jest-canvas-mock"], + setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"], setupFilesAfterEnv: ["/test/setupTests.ts"], moduleNameMapper: { "\\.(gif|png|ttf|woff2)$": "/__mocks__/imageMock.js", diff --git a/package.json b/package.json index 6e21f438d..62ce8627b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "matrix-react-sdk", "version": "0.0.0", - "version-matrix": "3.101.0", + "version-matrix": "3.104.0", "description": "SDK for matrix.org using React for Tchap", "author": "DINUM", "repository": { @@ -24,6 +24,9 @@ "package.json", ".stylelintrc.js" ], + "engines": { + "node": ">=20.0.0" + }, "main": "./lib/index.ts", "matrix_src_main": "./src/index.ts", "matrix_lib_main": "./lib/index.ts", @@ -56,29 +59,30 @@ "test:playwright:open": "yarn test:playwright --ui", "test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run", "test:playwright:screenshots:build": "docker build playwright -t matrix-react-sdk-playwright", - "test:playwright:screenshots:run": "docker run --rm --network host -v $(pwd)/../:/work/ -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it matrix-react-sdk-playwright", + "test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -v $(pwd)/../:/work/ -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it matrix-react-sdk-playwright", "coverage": "yarn test --coverage", "lint:workflows": "find .github/workflows -type f \\( -iname '*.yaml' -o -iname '*.yml' \\) | xargs -I {} sh -c 'echo \"Linting {}\"; action-validator \"{}\"'" }, "resolutions": { "@types/react-dom": "17.0.25", "@types/react": "17.0.80", - "@types/seedrandom": "3.0.4", + "@types/seedrandom": "3.0.8", "oidc-client-ts": "3.0.1", - "jwt-decode": "4.0.0" + "jwt-decode": "4.0.0", + "@floating-ui/react": "0.26.11", + "@radix-ui/react-id": "1.1.0" }, "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/analytics-events": "^0.21.0", + "@matrix-org/analytics-events": "^0.24.0", "@matrix-org/emojibase-bindings": "^1.1.2", - "@matrix-org/matrix-wysiwyg": "2.17.0", - "@matrix-org/olm": "3.2.15", + "@matrix-org/matrix-wysiwyg": "2.37.4", "@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/spec": "^1.7.0", - "@sentry/browser": "^7.0.0", + "@sentry/browser": "^8.0.0", "@testing-library/react-hooks": "^8.0.1", - "@vector-im/compound-design-tokens": "^1.2.0", - "@vector-im/compound-web": "^4.4.1", + "@vector-im/compound-design-tokens": "^1.6.1", + "@vector-im/compound-web": "^5.4.0", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-en": "^3.0.2", @@ -88,19 +92,19 @@ "classnames": "^2.2.6", "commonmark": "^0.31.0", "counterpart": "^0.18.6", + "css-tree": "^2.3.1", "diff-dom": "^5.0.0", "diff-match-patch": "^1.0.5", - "emojibase-regex": "15.3.0", + "emojibase-regex": "15.3.2", "escape-html": "^1.0.3", "file-saver": "^2.0.5", - "filesize": "10.1.2", - "gfm.css": "^1.1.2", + "filesize": "10.1.4", + "github-markdown-css": "^5.5.1", "glob-to-regexp": "^0.4.1", - "graphemer": "^1.4.0", "highlight.js": "^11.3.1", "html-entities": "^2.0.0", "is-ip": "^3.1.0", - "js-xxhash": "^3.0.1", + "js-xxhash": "^4.0.0", "jszip": "^3.7.0", "katex": "^0.16.0", "linkify-element": "4.1.3", @@ -111,7 +115,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "33.1.0", + "matrix-js-sdk": "34.2.0", "matrix-widget-api": "^1.5.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", @@ -119,8 +123,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.135.2", - "proposal-temporal": "^0.9.0", + "posthog-js": "1.145.0", "qrcode": "1.5.3", "re-resizable": "^6.9.0", "react": "17.0.2", @@ -133,8 +136,9 @@ "sanitize-filename": "^1.6.3", "sanitize-html": "2.13.0", "tar-js": "^0.3.0", + "temporal-polyfill": "^0.2.5", "ua-parser-js": "^1.0.2", - "uuid": "^9.0.0", + "uuid": "^10.0.0", "what-input": "^5.2.10" }, "devDependencies": { @@ -146,10 +150,10 @@ "@babel/eslint-parser": "^7.12.10", "@babel/eslint-plugin": "^7.12.10", "@babel/parser": "^7.12.11", - "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-export-default-from": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.7", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-transform-class-properties": "^7.12.1", + "@babel/plugin-transform-numeric-separator": "^7.12.7", + "@babel/plugin-transform-object-rest-spread": "^7.12.1", "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", @@ -165,6 +169,7 @@ "@types/commonmark": "^0.27.4", "@types/content-type": "^1.1.5", "@types/counterpart": "^0.18.1", + "@types/css-tree": "^2.3.8", "@types/diff-match-patch": "^1.0.32", "@types/escape-html": "^1.0.1", "@types/express": "^4.17.21", @@ -185,10 +190,10 @@ "@types/react-transition-group": "^4.4.0", "@types/sanitize-html": "2.11.0", "@types/sdp-transform": "^2.4.6", - "@types/seedrandom": "3.0.4", + "@types/seedrandom": "3.0.8", "@types/tar-js": "^0.3.2", "@types/ua-parser-js": "^0.7.36", - "@types/uuid": "^9.0.2", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "axe-core": "4.9.1", @@ -204,11 +209,12 @@ "eslint-plugin-matrix-org": "1.2.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-unicorn": "^53.0.0", + "eslint-plugin-unicorn": "^54.0.0", "express": "^4.18.2", - "fake-indexeddb": "^5.0.2", + "fake-indexeddb": "^6.0.0", "fetch-mock-jest": "^1.5.1", "fs-extra": "^11.0.0", + "glob": "^11.0.0", "jest": "^29.6.2", "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.6.2", @@ -219,15 +225,17 @@ "matrix-web-i18n": "^3.2.1", "mocha-junit-reporter": "^2.2.0", "node-fetch": "2", + "playwright-core": "^1.45.1", "postcss-scss": "^4.0.4", - "prettier": "3.2.5", + "prettier": "3.3.2", "raw-loader": "^4.0.2", - "rimraf": "^5.0.0", + "rimraf": "^6.0.0", "stylelint": "^16.1.0", "stylelint-config-standard": "^36.0.0", "stylelint-scss": "^6.0.0", "ts-node": "^10.9.1", - "typescript": "5.4.5" + "typescript": "5.5.3", + "web-streams-polyfill": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4.19", diff --git a/playwright.config.ts b/playwright.config.ts index 96a8dd95e..ba491ff82 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -16,11 +16,9 @@ limitations under the License. import { defineConfig } from "@playwright/test"; -import { TestOptions } from "./playwright/element-web-test"; - const baseURL = process.env["BASE_URL"] ?? "http://localhost:8080"; -export default defineConfig({ +export default defineConfig({ use: { viewport: { width: 1280, height: 720 }, ignoreHTTPSErrors: true, @@ -39,19 +37,10 @@ export default defineConfig({ }, testDir: "playwright/e2e", outputDir: "playwright/test-results", - workers: 1, + workers: process.env.CI ? "50%" : 1, retries: process.env.CI ? 2 : 0, reporter: process.env.CI ? [["blob"], ["github"]] : [["html", { outputFolder: "playwright/html-report" }]], - projects: [ - { - name: "Legacy Crypto", - use: { cryptoBackend: "legacy" }, - }, - { - name: "Rust Crypto", - use: { cryptoBackend: "rust" }, - }, - ], snapshotDir: "playwright/snapshots", snapshotPathTemplate: "{snapshotDir}/{testFilePath}/{arg}-{platform}{ext}", + forbidOnly: !!process.env.CI, }); diff --git a/res/css/views/rooms/_SpaceScopeHeader.pcss b/playwright/@types/playwright-core.d.ts similarity index 63% rename from res/css/views/rooms/_SpaceScopeHeader.pcss rename to playwright/@types/playwright-core.d.ts index 4a94793ba..0ef2ca0ec 100644 --- a/res/css/views/rooms/_SpaceScopeHeader.pcss +++ b/playwright/@types/playwright-core.d.ts @@ -1,5 +1,5 @@ /* -Copyright 2023 The Matrix.org Foundation C.I.C. +Copyright 2024 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SpaceScopeHeader { - text-align: center; - - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - overflow: hidden; - - .mx_BaseAvatar { - margin-right: var(--cpd-space-2x); - vertical-align: middle; - } +declare module "playwright-core/lib/utils" { + // This type is not public in playwright-core utils + export function sanitizeForFilePath(filePath: string): string; } diff --git a/playwright/Dockerfile b/playwright/Dockerfile index 7179e08ab..c5a9cdbb8 100644 --- a/playwright/Dockerfile +++ b/playwright/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.44.1-jammy +FROM mcr.microsoft.com/playwright:v1.45.1-jammy WORKDIR /work/matrix-react-sdk VOLUME ["/work/element-web/node_modules"] diff --git a/playwright/docker-entrypoint.sh b/playwright/docker-entrypoint.sh index 7bc5d2c9d..1e2ba553a 100644 --- a/playwright/docker-entrypoint.sh +++ b/playwright/docker-entrypoint.sh @@ -5,4 +5,4 @@ set -e yarn link yarn --cwd ../element-web install yarn --cwd ../element-web link matrix-react-sdk -npx playwright test --update-snapshots --reporter line --project='Legacy Crypto' $@ +npx playwright test --update-snapshots --reporter line $@ diff --git a/playwright/e2e/accessibility/keyboard-navigation.spec.ts b/playwright/e2e/accessibility/keyboard-navigation.spec.ts new file mode 100644 index 000000000..b4b74f518 --- /dev/null +++ b/playwright/e2e/accessibility/keyboard-navigation.spec.ts @@ -0,0 +1,166 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { test, expect } from "../../element-web-test"; +import { Bot } from "../../pages/bot"; + +test.describe("Landmark navigation tests", () => { + test.use({ + displayName: "Alice", + }); + + test("without any rooms", async ({ page, homeserver, app, user }) => { + /** + * Without any rooms, there is no tile in the roomlist to be focused. + * So the next landmark in the list should be focused instead. + */ + + // Pressing Control+F6 will first focus the space button + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + + // Pressing Control+F6 again will focus room search + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_RoomSearch")).toBeFocused(); + + // Pressing Control+F6 again will focus the message composer + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_HomePage")).toBeFocused(); + + // Pressing Control+F6 again will bring focus back to the space button + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + + // Now go back in the same order + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_HomePage")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_RoomSearch")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + }); + + test("with an open room", async ({ page, homeserver, app, user }) => { + const bob = new Bot(page, homeserver, { displayName: "Bob" }); + await bob.prepareClient(); + + // create dm with bob + await app.client.evaluate( + async (cli, { bob }) => { + const bobRoom = await cli.createRoom({ is_direct: true }); + await cli.invite(bobRoom.room_id, bob); + }, + { + bob: bob.credentials.userId, + }, + ); + + await app.viewRoomByName("Bob"); + // confirm the room was loaded + await expect(page.getByText("Bob joined the room")).toBeVisible(); + + // Pressing Control+F6 will first focus the space button + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + + // Pressing Control+F6 again will focus room search + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_RoomSearch")).toBeFocused(); + + // Pressing Control+F6 again will focus the room tile in the room list + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_RoomTile_selected")).toBeFocused(); + + // Pressing Control+F6 again will focus the message composer + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_BasicMessageComposer_input")).toBeFocused(); + + // Pressing Control+F6 again will bring focus back to the space button + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + + // Now go back in the same order + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_BasicMessageComposer_input")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_RoomTile_selected")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_RoomSearch")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + }); + + test("without an open room", async ({ page, homeserver, app, user }) => { + const bob = new Bot(page, homeserver, { displayName: "Bob" }); + await bob.prepareClient(); + + // create a dm with bob + await app.client.evaluate( + async (cli, { bob }) => { + const bobRoom = await cli.createRoom({ is_direct: true }); + await cli.invite(bobRoom.room_id, bob); + }, + { + bob: bob.credentials.userId, + }, + ); + + await app.viewRoomByName("Bob"); + // confirm the room was loaded + await expect(page.getByText("Bob joined the room")).toBeVisible(); + + // Close the room + page.goto("/#/home"); + + // Pressing Control+F6 will first focus the space button + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + + // Pressing Control+F6 again will focus room search + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_RoomSearch")).toBeFocused(); + + // Pressing Control+F6 again will focus the room tile in the room list + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_RoomTile")).toBeFocused(); + + // Pressing Control+F6 again will focus the home section + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_HomePage")).toBeFocused(); + + // Pressing Control+F6 will bring focus back to the space button + await page.keyboard.press("ControlOrMeta+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + + // Now go back in same order + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_HomePage")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_RoomTile")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_RoomSearch")).toBeFocused(); + + await page.keyboard.press("ControlOrMeta+Shift+F6"); + await expect(page.locator(".mx_SpaceButton_active")).toBeFocused(); + }); +}); diff --git a/playwright/e2e/app-loading/feature-detection.spec.ts b/playwright/e2e/app-loading/feature-detection.spec.ts new file mode 100644 index 000000000..2acde32c3 --- /dev/null +++ b/playwright/e2e/app-loading/feature-detection.spec.ts @@ -0,0 +1,42 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { test, expect } from "../../element-web-test"; + +test(`shows error page if browser lacks Intl support`, async ({ page }) => { + await page.addInitScript({ content: `delete window.Intl;` }); + await page.goto("/"); + + // Lack of Intl support causes the app bundle to fail to load, so we get the iframed + // static error page and need to explicitly look in the iframe becuse Playwright doesn't + // recurse into iframes when looking for elements + const header = await page.frameLocator("iframe").getByText("Unsupported browser"); + await expect(header).toBeVisible(); + + await expect(page).toMatchScreenshot("unsupported-browser.png"); +}); + +test(`shows error page if browser lacks WebAssembly support`, async ({ page }) => { + await page.addInitScript({ content: `delete window.WebAssembly;` }); + await page.goto("/"); + + // Lack of WebAssembly support doesn't cause the bundle to fail loading, so we get + // CompatibilityView, ie. no iframes. + const header = await page.getByText("Unsupported browser"); + await expect(header).toBeVisible(); + + await expect(page).toMatchScreenshot("unsupported-browser-CompatibilityView.png"); +}); diff --git a/playwright/e2e/app-loading/guest-registration.spec.ts b/playwright/e2e/app-loading/guest-registration.spec.ts new file mode 100644 index 000000000..17ec36935 --- /dev/null +++ b/playwright/e2e/app-loading/guest-registration.spec.ts @@ -0,0 +1,45 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* + * Tests for application startup with guest registration enabled on the server. + */ + +import { expect, test } from "../../element-web-test"; + +test.use({ + startHomeserverOpts: "guest-enabled", + config: async ({ homeserver }, use) => { + await use({ + default_server_config: { + "m.homeserver": { base_url: homeserver.config.baseUrl }, + }, + }); + }, +}); + +test("Shows the welcome page by default", async ({ page }) => { + await page.goto("/"); + await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Sign in" })).toBeVisible(); +}); + +test("Room link correctly loads a room view", async ({ page }) => { + await page.goto("/#/room/!room:id"); + await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); + await expect(page).toHaveURL(/\/#\/room\/!room:id$/); + await expect(page.getByRole("heading", { name: "Join the conversation with an account" })).toBeVisible(); +}); diff --git a/playwright/e2e/app-loading/stored-credentials.spec.ts b/playwright/e2e/app-loading/stored-credentials.spec.ts new file mode 100644 index 000000000..f720a5455 --- /dev/null +++ b/playwright/e2e/app-loading/stored-credentials.spec.ts @@ -0,0 +1,68 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { expect, test } from "../../element-web-test"; +import { ElementAppPage } from "../../pages/ElementAppPage"; + +/* + * Tests for application startup with credentials stored in localstorage. + */ + +test.use({ displayName: "Boris" }); + +test("Shows the homepage by default", async ({ pageWithCredentials: page }) => { + await page.goto("/"); + await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); + + await expect(page).toHaveURL(/\/#\/home/); + await expect(page.getByRole("heading", { name: "Welcome Boris", exact: true })).toBeVisible(); +}); + +test("Shows the last known page on reload", async ({ pageWithCredentials: page }) => { + await page.goto("/"); + await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); + + const app = new ElementAppPage(page); + await app.client.createRoom({ name: "Test Room" }); + await app.viewRoomByName("Test Room"); + + // Navigate away + await page.goto("about:blank"); + + // And back again + await page.goto("/"); + await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); + + // Check that the room reloaded + await expect(page).toHaveURL(/\/#\/room\//); + await expect(page.locator(".mx_RoomHeader")).toContainText("Test Room"); +}); + +test("Room link correctly loads a room view", async ({ pageWithCredentials: page }) => { + await page.goto("/#/room/!room:id"); + await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); + + await expect(page).toHaveURL(/\/#\/room\/!room:id$/); + await expect(page.getByRole("button", { name: "Join the discussion" })).toBeVisible(); +}); + +test("Login link redirects to home page", async ({ pageWithCredentials: page }) => { + await page.goto("/#/login"); + await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); + + await expect(page).toHaveURL(/\/#\/home/); + await expect(page.getByRole("heading", { name: "Welcome Boris", exact: true })).toBeVisible(); +}); diff --git a/playwright/e2e/audio-player/audio-player.spec.ts b/playwright/e2e/audio-player/audio-player.spec.ts index 4581801db..e60d27329 100644 --- a/playwright/e2e/audio-player/audio-player.spec.ts +++ b/playwright/e2e/audio-player/audio-player.spec.ts @@ -160,7 +160,7 @@ test.describe("Audio player", () => { // Enable high contrast manually const settings = await app.settings.openUserSettings("Appearance"); - await settings.getByTestId("mx_ThemeChoicePanel").getByText("Use high contrast").click(); + await settings.getByRole("radio", { name: "High contrast" }).click(); await app.closeDialog(); diff --git a/playwright/e2e/chat-export/html-export.spec.ts b/playwright/e2e/chat-export/html-export.spec.ts new file mode 100644 index 000000000..b142fcec4 --- /dev/null +++ b/playwright/e2e/chat-export/html-export.spec.ts @@ -0,0 +1,133 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 os from "node:os"; +import path from "node:path"; +import * as fsp from "node:fs/promises"; +import * as fs from "node:fs"; +import JSZip from "jszip"; + +import { test, expect } from "../../element-web-test"; + +// Based on https://github.com/Stuk/jszip/issues/466#issuecomment-2097061912 +async function extractZipFileToPath(file: string, outputPath: string): Promise { + if (!fs.existsSync(outputPath)) { + fs.mkdirSync(outputPath, { recursive: true }); + } + + const data = await fsp.readFile(file); + const zip = await JSZip.loadAsync(data, { createFolders: true }); + + await new Promise((resolve, reject) => { + let entryCount = 0; + let errorOut = false; + + zip.forEach(() => { + entryCount++; + }); // there is no other way to count the number of entries within the zip file. + + zip.forEach((relativePath, zipEntry) => { + if (errorOut) { + return; + } + + const outputEntryPath = path.join(outputPath, relativePath); + if (zipEntry.dir) { + if (!fs.existsSync(outputEntryPath)) { + fs.mkdirSync(outputEntryPath, { recursive: true }); + } + + entryCount--; + + if (entryCount === 0) { + resolve(); + } + } else { + void zipEntry + .async("blob") + .then(async (content) => Buffer.from(await content.arrayBuffer())) + .then((buffer) => { + const stream = fs.createWriteStream(outputEntryPath); + stream.write(buffer, (error) => { + if (error) { + reject(error); + errorOut = true; + } + }); + stream.on("finish", () => { + entryCount--; + + if (entryCount === 0) { + resolve(); + } + }); + stream.end(); // extremely important on Windows. On Mac / Linux, not so much since those platforms allow multiple apps to read from the same file. Windows doesn't allow that. + }) + .catch((e) => { + errorOut = true; + reject(e); + }); + } + }); + }); + + return zip; +} + +test.describe("HTML Export", () => { + test.use({ + displayName: "Alice", + room: async ({ app, user }, use) => { + const roomId = await app.client.createRoom({ name: "Important Room" }); + await app.viewRoomByName("Important Room"); + await use({ roomId }); + }, + }); + + test("should export html successfully and match screenshot", async ({ page, app, room }) => { + // Send a bunch of messages to populate the room + for (let i = 1; i < 10; i++) { + await app.client.sendMessage(room.roomId, { body: `Testing ${i}`, msgtype: "m.text" }); + } + + // Wait for all the messages to be displayed + await expect( + page.locator(".mx_EventTile_last .mx_MTextBody .mx_EventTile_body").getByText("Testing 9"), + ).toBeVisible(); + + await app.toggleRoomInfoPanel(); + await page.getByRole("menuitem", { name: "Export Chat" }).click(); + + const downloadPromise = page.waitForEvent("download"); + await page.getByRole("button", { name: "Export", exact: true }).click(); + const download = await downloadPromise; + + const dirPath = path.join(os.tmpdir(), "html-export-test"); + const zipPath = `${dirPath}.zip`; + await download.saveAs(zipPath); + + const zip = await extractZipFileToPath(zipPath, dirPath); + await page.goto(`file://${dirPath}/${Object.keys(zip.files)[0]}/messages.html`); + await expect(page).toMatchScreenshot("html-export.png", { + mask: [ + page.getByText("This is the start of export", { exact: false }), + // We need to mask the whole thing because the width of the time part changes + page.locator(".mx_TimelineSeparator"), + page.locator(".mx_MessageTimestamp"), + ], + }); + }); +}); diff --git a/playwright/e2e/composer/CIDER.spec.ts b/playwright/e2e/composer/CIDER.spec.ts new file mode 100644 index 000000000..779babdaf --- /dev/null +++ b/playwright/e2e/composer/CIDER.spec.ts @@ -0,0 +1,106 @@ +/* +Copyright 2022 - 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { test, expect } from "../../element-web-test"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; + +const CtrlOrMeta = process.platform === "darwin" ? "Meta" : "Control"; + +test.describe("Composer", () => { + test.use({ + displayName: "Janet", + }); + + test.use({ + room: async ({ app, user }, use) => { + const roomId = await app.client.createRoom({ name: "Composing Room" }); + await app.viewRoomByName("Composing Room"); + await use({ roomId }); + }, + }); + + test.beforeEach(async ({ room }) => {}); // trigger room fixture + + test.describe("CIDER", () => { + test("sends a message when you click send or press Enter", async ({ page }) => { + const composer = page.getByRole("textbox", { name: "Send a message…" }); + + // Type a message + await composer.pressSequentially("my message 0"); + // It has not been sent yet + await expect(page.locator(".mx_EventTile_body", { hasText: "my message 0" })).not.toBeVisible(); + + // Click send + await page.getByRole("button", { name: "Send message" }).click(); + // It has been sent + await expect( + page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 0" }), + ).toBeVisible(); + + // Type another and press Enter afterward + await composer.pressSequentially("my message 1"); + await composer.press("Enter"); + // It was sent + await expect( + page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 1" }), + ).toBeVisible(); + }); + + test("can write formatted text", async ({ page }) => { + const composer = page.getByRole("textbox", { name: "Send a message…" }); + + await composer.pressSequentially("my bold"); + await composer.press(`${CtrlOrMeta}+KeyB`); + await composer.pressSequentially(" message"); + await page.getByRole("button", { name: "Send message" }).click(); + // Note: both "bold" and "message" are bold, which is probably surprising + await expect(page.locator(".mx_EventTile_body strong", { hasText: "bold message" })).toBeVisible(); + }); + + test("should allow user to input emoji via graphical picker", async ({ page, app }) => { + await app.getComposer(false).getByRole("button", { name: "Emoji" }).click(); + + await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click(); + + await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker + await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message + + await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible(); + }); + + test.describe("when Control+Enter is required to send", () => { + test.beforeEach(async ({ app }) => { + await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true); + }); + + test("only sends when you press Control+Enter", async ({ page }) => { + const composer = page.getByRole("textbox", { name: "Send a message…" }); + // Type a message and press Enter + await composer.pressSequentially("my message 3"); + await composer.press("Enter"); + // It has not been sent yet + await expect(page.locator(".mx_EventTile_body", { hasText: "my message 3" })).not.toBeVisible(); + + // Press Control+Enter + await composer.press(`${CtrlOrMeta}+Enter`); + // It was sent + await expect( + page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 3" }), + ).toBeVisible(); + }); + }); + }); +}); diff --git a/playwright/e2e/composer/composer.spec.ts b/playwright/e2e/composer/RTE.spec.ts similarity index 80% rename from playwright/e2e/composer/composer.spec.ts rename to playwright/e2e/composer/RTE.spec.ts index e7be457f8..53599d532 100644 --- a/playwright/e2e/composer/composer.spec.ts +++ b/playwright/e2e/composer/RTE.spec.ts @@ -34,76 +34,6 @@ test.describe("Composer", () => { test.beforeEach(async ({ room }) => {}); // trigger room fixture - test.describe("CIDER", () => { - test("sends a message when you click send or press Enter", async ({ page }) => { - const composer = page.getByRole("textbox", { name: "Send a message…" }); - - // Type a message - await composer.pressSequentially("my message 0"); - // It has not been sent yet - await expect(page.locator(".mx_EventTile_body", { hasText: "my message 0" })).not.toBeVisible(); - - // Click send - await page.getByRole("button", { name: "Send message" }).click(); - // It has been sent - await expect( - page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 0" }), - ).toBeVisible(); - - // Type another and press Enter afterward - await composer.pressSequentially("my message 1"); - await composer.press("Enter"); - // It was sent - await expect( - page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 1" }), - ).toBeVisible(); - }); - - test("can write formatted text", async ({ page }) => { - const composer = page.getByRole("textbox", { name: "Send a message…" }); - - await composer.pressSequentially("my bold"); - await composer.press(`${CtrlOrMeta}+KeyB`); - await composer.pressSequentially(" message"); - await page.getByRole("button", { name: "Send message" }).click(); - // Note: both "bold" and "message" are bold, which is probably surprising - await expect(page.locator(".mx_EventTile_body strong", { hasText: "bold message" })).toBeVisible(); - }); - - test("should allow user to input emoji via graphical picker", async ({ page, app }) => { - await app.getComposer(false).getByRole("button", { name: "Emoji" }).click(); - - await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click(); - - await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker - await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message - - await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible(); - }); - - test.describe("when Control+Enter is required to send", () => { - test.beforeEach(async ({ app }) => { - await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true); - }); - - test("only sends when you press Control+Enter", async ({ page }) => { - const composer = page.getByRole("textbox", { name: "Send a message…" }); - // Type a message and press Enter - await composer.pressSequentially("my message 3"); - await composer.press("Enter"); - // It has not been sent yet - await expect(page.locator(".mx_EventTile_body", { hasText: "my message 3" })).not.toBeVisible(); - - // Press Control+Enter - await composer.press(`${CtrlOrMeta}+Enter`); - // It was sent - await expect( - page.locator(".mx_EventTile_last .mx_EventTile_body", { hasText: "my message 3" }), - ).toBeVisible(); - }); - }); - }); - test.describe("Rich text editor", () => { test.use({ labsFlags: ["feature_wysiwyg_composer"], diff --git a/playwright/e2e/create-room/create-room.spec.ts b/playwright/e2e/create-room/create-room.spec.ts index 0e5882e23..651439302 100644 --- a/playwright/e2e/create-room/create-room.spec.ts +++ b/playwright/e2e/create-room/create-room.spec.ts @@ -36,7 +36,7 @@ test.describe("Create Room", () => { await dialog.getByRole("button", { name: "Create room" }).click(); await expect(page).toHaveURL(/\/#\/room\/#test-room-1:localhost/); - const header = page.locator(".mx_LegacyRoomHeader"); + const header = page.locator(".mx_RoomHeader"); await expect(header).toContainText(name); await expect(header).toContainText(topic); }); diff --git a/playwright/e2e/crypto/crypto.spec.ts b/playwright/e2e/crypto/crypto.spec.ts index 326aeaff8..3f4621f90 100644 --- a/playwright/e2e/crypto/crypto.spec.ts +++ b/playwright/e2e/crypto/crypto.spec.ts @@ -15,29 +15,10 @@ limitations under the License. */ import type { Page } from "@playwright/test"; -import type { EmittedEvents, Preset } from "matrix-js-sdk/src/matrix"; import { expect, test } from "../../element-web-test"; -import { - copyAndContinue, - createRoom, - createSharedRoomWithUser, - doTwoWaySasVerification, - enableKeyBackup, - logIntoElement, - logOutOfElement, - sendMessageInCurrentRoom, - verifySession, - waitForVerificationRequest, -} from "./utils"; +import { autoJoin, copyAndContinue, createSharedRoomWithUser, enableKeyBackup, verify } from "./utils"; import { Bot } from "../../pages/bot"; import { ElementAppPage } from "../../pages/ElementAppPage"; -import { Client } from "../../pages/client"; -import { isDendrite } from "../../plugins/homeserver/dendrite"; - -const openRoomInfo = async (page: Page) => { - await page.getByRole("button", { name: "Room info" }).click(); - return page.locator(".mx_RightPanel"); -}; const checkDMRoom = async (page: Page) => { const body = page.locator(".mx_RoomView_body"); @@ -88,38 +69,6 @@ const bobJoin = async (page: Page, bob: Bot) => { return roomId; }; -/** configure the given MatrixClient to auto-accept any invites */ -async function autoJoin(client: Client) { - await client.evaluate((cli) => { - cli.on(window.matrixcs.RoomMemberEvent.Membership, (event, member) => { - if (member.membership === "invite" && member.userId === cli.getUserId()) { - cli.joinRoom(member.roomId); - } - }); - }); -} - -const verify = async (page: Page, bob: Bot) => { - const bobsVerificationRequestPromise = waitForVerificationRequest(bob); - - const roomInfo = await openRoomInfo(page); - await roomInfo.getByRole("menuitem", { name: "People" }).click(); - await roomInfo.getByText("Bob").click(); - await roomInfo.getByRole("button", { name: "Verify" }).click(); - await roomInfo.getByRole("button", { name: "Start Verification" }).click(); - - // this requires creating a DM, so can take a while. Give it a longer timeout. - await roomInfo.getByRole("button", { name: "Verify by emoji" }).click({ timeout: 30000 }); - - const request = await bobsVerificationRequestPromise; - // the bot user races with the Element user to hit the "verify by emoji" button - const verifier = await request.evaluateHandle((request) => request.startVerification("m.sas.v1")); - await doTwoWaySasVerification(page, verifier); - await roomInfo.getByRole("button", { name: "They match" }).click(); - await expect(roomInfo.getByText("You've successfully verified Bob!")).toBeVisible(); - await roomInfo.getByRole("button", { name: "Got it" }).click(); -}; - test.describe("Cryptography", function () { test.use({ displayName: "Alice", @@ -248,6 +197,10 @@ test.describe("Cryptography", function () { await page.getByPlaceholder("Security Key").fill(secretStorageKey); await page.getByRole("button", { name: "Continue" }).click(); + // Enter the password + await page.getByPlaceholder("Password").fill(aliceCredentials.password); + await page.getByRole("button", { name: "Continue" }).click(); + await expect(async () => { const masterKey2 = await fetchMasterKey(); expect(masterKey1).not.toEqual(masterKey2); @@ -271,11 +224,11 @@ test.describe("Cryptography", function () { await checkDMRoom(page); const bobRoomId = await bobJoin(page, bob); await testMessages(page, bob, bobRoomId); - await verify(page, bob); + await verify(app, bob); // Assert that verified icon is rendered - await page.getByRole("button", { name: "Room members" }).click(); - await page.getByRole("button", { name: "Room information" }).click(); + await page.getByTestId("base-card-back-button").click(); + await page.locator(".mx_RightPanelTabs").getByText("Info").click(); await expect(page.locator('.mx_RoomSummaryCard_badges [data-kind="success"]')).toContainText("Encrypted"); // Take a snapshot of RoomSummaryCard with a verified E2EE icon @@ -293,525 +246,6 @@ test.describe("Cryptography", function () { // we need to have a room with the other user present, so we can open the verification panel await createSharedRoomWithUser(app, bob.credentials.userId); - await verify(page, bob); - }); - - test.describe("event shields", () => { - let testRoomId: string; - - test.beforeEach(async ({ page, bot: bob, user: aliceCredentials, app }) => { - await app.client.bootstrapCrossSigning(aliceCredentials); - await autoJoin(bob); - - // create an encrypted room - testRoomId = await createSharedRoomWithUser(app, bob.credentials.userId, { - name: "TestRoom", - initial_state: [ - { - type: "m.room.encryption", - state_key: "", - content: { - algorithm: "m.megolm.v1.aes-sha2", - }, - }, - ], - }); - }); - - test("should show the correct shield on e2e events", async ({ - page, - app, - bot: bob, - homeserver, - cryptoBackend, - }) => { - // Bob has a second, not cross-signed, device - const bobSecondDevice = new Bot(page, homeserver, { - bootstrapSecretStorage: false, - bootstrapCrossSigning: false, - }); - bobSecondDevice.setCredentials( - await homeserver.loginUser(bob.credentials.userId, bob.credentials.password), - ); - await bobSecondDevice.prepareClient(); - - await bob.sendEvent(testRoomId, null, "m.room.encrypted", { - algorithm: "m.megolm.v1.aes-sha2", - ciphertext: "the bird is in the hand", - }); - - const last = page.locator(".mx_EventTile_last"); - await expect(last).toContainText("Unable to decrypt message"); - const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/); - await lastE2eIcon.focus(); - await expect(page.getByRole("tooltip")).toContainText("This message could not be decrypted"); - - /* Should show a red padlock for an unencrypted message in an e2e room */ - await bob.evaluate( - (cli, testRoomId) => - cli.http.authedRequest( - window.matrixcs.Method.Put, - `/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`, - undefined, - { - msgtype: "m.text", - body: "test unencrypted", - }, - ), - testRoomId, - ); - - await expect(last).toContainText("test unencrypted"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastE2eIcon.focus(); - await expect(page.getByRole("tooltip")).toContainText("Not encrypted"); - - /* Should show no padlock for an unverified user */ - // bob sends a valid event - await bob.sendMessage(testRoomId, "test encrypted 1"); - - // the message should appear, decrypted, with no warning, but also no "verified" - const lastTile = page.locator(".mx_EventTile_last"); - const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); - await expect(lastTile).toContainText("test encrypted 1"); - // no e2e icon - await expect(lastTileE2eIcon).not.toBeVisible(); - - /* Now verify Bob */ - await verify(page, bob); - - /* Existing message should be updated when user is verified. */ - await expect(last).toContainText("test encrypted 1"); - // still no e2e icon - await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible(); - - /* should show no padlock, and be verified, for a message from a verified device */ - await bob.sendMessage(testRoomId, "test encrypted 2"); - - await expect(lastTile).toContainText("test encrypted 2"); - // no e2e icon - await expect(lastTileE2eIcon).not.toBeVisible(); - - /* should show red padlock for a message from an unverified device */ - await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); - await expect(lastTile).toContainText("test encrypted from unverified"); - await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastTileE2eIcon.focus(); - await expect(page.getByRole("tooltip")).toContainText("Encrypted by a device not verified by its owner."); - - /* Should show a grey padlock for a message from an unknown device */ - // bob deletes his second device - await bobSecondDevice.evaluate((cli) => cli.logout(true)); - - // wait for the logout to propagate. Workaround for https://github.com/vector-im/element-web/issues/26263 by repeatedly closing and reopening Bob's user info. - async function awaitOneDevice(iterations = 1) { - const rightPanel = page.locator(".mx_RightPanel"); - await rightPanel.getByRole("button", { name: "Room members" }).click(); - await rightPanel.getByText("Bob").click(); - const sessionCountText = await rightPanel - .locator(".mx_UserInfo_devices") - .getByText(" session", { exact: false }) - .textContent(); - // cf https://github.com/vector-im/element-web/issues/26279: Element-R uses the wrong text here - if (sessionCountText != "1 session" && sessionCountText != "1 verified session") { - if (iterations >= 10) { - throw new Error(`Bob still has ${sessionCountText} after 10 iterations`); - } - await awaitOneDevice(iterations + 1); - } - } - - await awaitOneDevice(); - - // close and reopen the room, to get the shield to update. - await app.viewRoomByName("Bob"); - await app.viewRoomByName("TestRoom"); - - // some debate over whether this should have a red or a grey shield. Legacy crypto shows a grey shield, - // Rust crypto a red one. - await expect(last).toContainText("test encrypted from unverified"); - if (cryptoBackend === "rust") { - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - } else { - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_normal/); - } - await lastE2eIcon.focus(); - await expect(page.getByRole("tooltip")).toContainText("Encrypted by an unknown or deleted device."); - }); - - test("Should show a grey padlock for a key restored from backup", async ({ - page, - app, - bot: bob, - homeserver, - user: aliceCredentials, - }) => { - const securityKey = await enableKeyBackup(app); - - // bob sends a valid event - await bob.sendMessage(testRoomId, "test encrypted 1"); - - const lastTile = page.locator(".mx_EventTile_last"); - const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); - await expect(lastTile).toContainText("test encrypted 1"); - // no e2e icon - await expect(lastTileE2eIcon).not.toBeVisible(); - - // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for - // the key to be backed up. - await page.waitForTimeout(10000); - - /* log out, and back in */ - await logOutOfElement(page); - await logIntoElement(page, homeserver, aliceCredentials, securityKey); - - /* go back to the test room and find Bob's message again */ - await app.viewRoomById(testRoomId); - await expect(lastTile).toContainText("test encrypted 1"); - // The gray shield would be a mx_EventTile_e2eIcon_normal. The red shield would be a mx_EventTile_e2eIcon_warning. - // No shield would have no div mx_EventTile_e2eIcon at all. - await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_normal/); - await lastTileE2eIcon.hover(); - // The key is coming from backup, so it is not anymore possible to establish if the claimed device - // creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device." - // It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted. - await expect(page.getByRole("tooltip")).toContainText( - "The authenticity of this encrypted message can't be guaranteed on this device.", - ); - }); - - test("should show the correct shield on edited e2e events", async ({ page, app, bot: bob, homeserver }) => { - // bob has a second, not cross-signed, device - const bobSecondDevice = new Bot(page, homeserver, { - bootstrapSecretStorage: false, - bootstrapCrossSigning: false, - }); - bobSecondDevice.setCredentials( - await homeserver.loginUser(bob.credentials.userId, bob.credentials.password), - ); - await bobSecondDevice.prepareClient(); - - // verify Bob - await verify(page, bob); - - // bob sends a valid event - const testEvent = await bob.sendMessage(testRoomId, "Hoo!"); - - // the message should appear, decrypted, with no warning - await expect( - page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"), - ).not.toBeVisible(); - - // bob sends an edit to the first message with his unverified device - await bobSecondDevice.sendMessage(testRoomId, { - "m.new_content": { - msgtype: "m.text", - body: "Haa!", - }, - "m.relates_to": { - rel_type: "m.replace", - event_id: testEvent.event_id, - }, - }); - - // the edit should have a warning - await expect( - page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon_warning"), - ).toBeVisible(); - - // a second edit from the verified device should be ok - await bob.sendMessage(testRoomId, { - "m.new_content": { - msgtype: "m.text", - body: "Hee!", - }, - "m.relates_to": { - rel_type: "m.replace", - event_id: testEvent.event_id, - }, - }); - - await expect( - page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon_warning"), - ).not.toBeVisible(); - }); - }); - - test.describe("decryption failure messages", () => { - test("should handle device-relative historical messages", async ({ - homeserver, - page, - app, - credentials, - user, - cryptoBackend, - }) => { - test.skip(cryptoBackend === "legacy", "Not implemented for legacy crypto"); - test.setTimeout(60000); - - // Start with a logged-in session, without key backup, and send a message. - await createRoom(page, "Test room", true); - await sendMessageInCurrentRoom(page, "test test"); - - // Log out, discarding the key for the sent message. - await logOutOfElement(page, true); - - // Log in again, and see how the message looks. - await logIntoElement(page, homeserver, credentials); - await app.viewRoomByName("Test room"); - const lastTile = page.locator(".mx_EventTile").last(); - await expect(lastTile).toContainText("Historical messages are not available on this device"); - await expect(lastTile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); - - // Now, we set up key backup, and then send another message. - const secretStorageKey = await enableKeyBackup(app); - await app.viewRoomByName("Test room"); - await sendMessageInCurrentRoom(page, "test2 test2"); - - // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for - // the key to be backed up. - await page.waitForTimeout(10000); - - // Finally, log out again, and back in, skipping verification for now, and see what we see. - await logOutOfElement(page); - await logIntoElement(page, homeserver, credentials); - await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click(); - await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click(); - await app.viewRoomByName("Test room"); - - // There should be two historical events in the timeline - const tiles = await page.locator(".mx_EventTile").all(); - expect(tiles.length).toBeGreaterThanOrEqual(2); - // look at the last two tiles only - for (const tile of tiles.slice(-2)) { - await expect(tile).toContainText("You need to verify this device for access to historical messages"); - await expect(tile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); - } - - // Now verify our device (setting up key backup), and check what happens - await verifySession(app, secretStorageKey); - const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2); - - // The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though. - await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message"); - await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); - - // The second message should now be decrypted, with a grey shield - await expect(tilesAfterVerify[1]).toContainText("test2 test2"); - await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon_normal")).toBeVisible(); - }); - - test.describe("non-joined historical messages", () => { - test.skip(isDendrite, "does not yet support membership on events"); - - test("should display undecryptable non-joined historical messages with a different message", async ({ - homeserver, - page, - app, - credentials: aliceCredentials, - user: alice, - cryptoBackend, - bot: bob, - }) => { - test.skip(cryptoBackend === "legacy", "Not implemented for legacy crypto"); - - // Bob creates an encrypted room and sends a message to it. He then invites Alice - const roomId = await bob.evaluate( - async (client, { alice }) => { - const encryptionStatePromise = new Promise((resolve) => { - client.on("RoomState.events" as EmittedEvents, (event, _state, _lastStateEvent) => { - if (event.getType() === "m.room.encryption") { - resolve(); - } - }); - }); - - const { room_id: roomId } = await client.createRoom({ - initial_state: [ - { - type: "m.room.encryption", - content: { - algorithm: "m.megolm.v1.aes-sha2", - }, - }, - ], - name: "Test room", - preset: "private_chat" as Preset, - }); - - // wait for m.room.encryption event, so that when we send a - // message, it will be encrypted - await encryptionStatePromise; - - await client.sendTextMessage(roomId, "This should be undecryptable"); - - await client.invite(roomId, alice.userId); - - return roomId; - }, - { alice }, - ); - - // Alice accepts the invite - await expect( - page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"), - ).toHaveCount(1); - await page.getByRole("treeitem", { name: "Test room" }).click(); - await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); - - // Bob sends an encrypted event and an undecryptable event - await bob.evaluate( - async (client, { roomId }) => { - await client.sendTextMessage(roomId, "This should be decryptable"); - await client.sendEvent( - roomId, - "m.room.encrypted" as any, - { - algorithm: "m.megolm.v1.aes-sha2", - ciphertext: "this+message+will+be+undecryptable", - device_id: client.getDeviceId()!, - sender_key: (await client.getCrypto()!.getOwnDeviceKeys()).ed25519, - session_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - } as any, - ); - }, - { roomId }, - ); - - // We wait for the event tiles that we expect from the messages that - // Bob sent, in sequence. - await expect( - page.locator(`.mx_EventTile`).getByText("You don't have access to this message"), - ).toBeVisible(); - await expect(page.locator(`.mx_EventTile`).getByText("This should be decryptable")).toBeVisible(); - await expect(page.locator(`.mx_EventTile`).getByText("Unable to decrypt message")).toBeVisible(); - - // And then we ensure that they are where we expect them to be - // Alice should see these event tiles: - // - first message sent by Bob (undecryptable) - // - Bob invited Alice - // - Alice joined the room - // - second message sent by Bob (decryptable) - // - third message sent by Bob (undecryptable) - const tiles = await page.locator(".mx_EventTile").all(); - expect(tiles.length).toBeGreaterThanOrEqual(5); - - // The first message from Bob was sent before Alice was in the room, so should - // be different from the standard UTD message - await expect(tiles[tiles.length - 5]).toContainText("You don't have access to this message"); - await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); - - // The second message from Bob should be decryptable - await expect(tiles[tiles.length - 2]).toContainText("This should be decryptable"); - // this tile won't have an e2e icon since we got the key from the sender - - // The third message from Bob is undecryptable, but was sent while Alice was - // in the room and is expected to be decryptable, so this should have the - // standard UTD message - await expect(tiles[tiles.length - 1]).toContainText("Unable to decrypt message"); - await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); - }); - - test("should be able to jump to a message sent before our last join event", async ({ - homeserver, - page, - app, - credentials: aliceCredentials, - user: alice, - cryptoBackend, - bot: bob, - }) => { - // The old pre-join UTD hiding code would hide events sent - // before our latest join event, even if the event that we're - // jumping to was decryptable. We test that this no longer happens. - - test.skip(cryptoBackend === "legacy", "Not implemented for legacy crypto"); - - // Bob: - // - creates an encrypted room, - // - invites Alice, - // - sends a message to it, - // - kicks Alice, - // - sends a bunch more events - // - invites Alice again - // In this way, there will be an event that Alice can decrypt, - // followed by a bunch of undecryptable events which Alice shouldn't - // expect to be able to decrypt. The old code would have hidden all - // the events, even the decryptable event (which it wouldn't have - // even tried to fetch, if it was far enough back). - const { roomId, eventId } = await bob.evaluate( - async (client, { alice }) => { - const { room_id: roomId } = await client.createRoom({ - initial_state: [ - { - type: "m.room.encryption", - content: { - algorithm: "m.megolm.v1.aes-sha2", - }, - }, - ], - name: "Test room", - preset: "private_chat" as Preset, - }); - - // invite Alice - const inviteAlicePromise = new Promise((resolve) => { - client.on("RoomMember.membership" as EmittedEvents, (_event, member, _oldMembership?) => { - if (member.userId === alice.userId && member.membership === "invite") { - resolve(); - } - }); - }); - await client.invite(roomId, alice.userId); - // wait for the invite to come back so that we encrypt to Alice - await inviteAlicePromise; - - // send a message that Alice should be able to decrypt - const { event_id: eventId } = await client.sendTextMessage( - roomId, - "This should be decryptable", - ); - - // kick Alice - const kickAlicePromise = new Promise((resolve) => { - client.on("RoomMember.membership" as EmittedEvents, (_event, member, _oldMembership?) => { - if (member.userId === alice.userId && member.membership === "leave") { - resolve(); - } - }); - }); - await client.kick(roomId, alice.userId); - await kickAlicePromise; - - // send a bunch of messages that Alice won't be able to decrypt - for (let i = 0; i < 20; i++) { - await client.sendTextMessage(roomId, `${i}`); - } - - // invite Alice again - await client.invite(roomId, alice.userId); - - return { roomId, eventId }; - }, - { alice }, - ); - - // Alice accepts the invite - await expect( - page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"), - ).toHaveCount(1); - await page.getByRole("treeitem", { name: "Test room" }).click(); - await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); - - // wait until we're joined and see the timeline - await expect(page.locator(`.mx_EventTile`).getByText("Alice joined the room")).toBeVisible(); - - // we should be able to jump to the decryptable message that Bob sent - await page.goto(`#/room/${roomId}/${eventId}`); - - await expect(page.locator(`.mx_EventTile`).getByText("This should be decryptable")).toBeVisible(); - }); - }); + await verify(app, bob); }); }); diff --git a/playwright/e2e/crypto/decryption-failure-messages.spec.ts b/playwright/e2e/crypto/decryption-failure-messages.spec.ts new file mode 100644 index 000000000..bcefa947a --- /dev/null +++ b/playwright/e2e/crypto/decryption-failure-messages.spec.ts @@ -0,0 +1,302 @@ +/* +Copyright 2022-2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 type { EmittedEvents, Preset } from "matrix-js-sdk/src/matrix"; +import { expect, test } from "../../element-web-test"; +import { + createRoom, + enableKeyBackup, + logIntoElement, + logOutOfElement, + sendMessageInCurrentRoom, + verifySession, +} from "./utils"; +import { isDendrite } from "../../plugins/homeserver/dendrite"; + +test.describe("Cryptography", function () { + test.use({ + displayName: "Alice", + botCreateOpts: { + displayName: "Bob", + autoAcceptInvites: false, + }, + }); + + test.describe("decryption failure messages", () => { + test("should handle device-relative historical messages", async ({ + homeserver, + page, + app, + credentials, + user, + }) => { + test.setTimeout(60000); + + // Start with a logged-in session, without key backup, and send a message. + await createRoom(page, "Test room", true); + await sendMessageInCurrentRoom(page, "test test"); + + // Log out, discarding the key for the sent message. + await logOutOfElement(page, true); + + // Log in again, and see how the message looks. + await logIntoElement(page, homeserver, credentials); + await app.viewRoomByName("Test room"); + const lastTile = page.locator(".mx_EventTile").last(); + await expect(lastTile).toContainText("Historical messages are not available on this device"); + await expect(lastTile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); + + // Now, we set up key backup, and then send another message. + const secretStorageKey = await enableKeyBackup(app); + await app.viewRoomByName("Test room"); + await sendMessageInCurrentRoom(page, "test2 test2"); + + // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for + // the key to be backed up. + await page.waitForTimeout(10000); + + // Finally, log out again, and back in, skipping verification for now, and see what we see. + await logOutOfElement(page); + await logIntoElement(page, homeserver, credentials); + await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click(); + await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click(); + await app.viewRoomByName("Test room"); + + // There should be two historical events in the timeline + const tiles = await page.locator(".mx_EventTile").all(); + expect(tiles.length).toBeGreaterThanOrEqual(2); + // look at the last two tiles only + for (const tile of tiles.slice(-2)) { + await expect(tile).toContainText("You need to verify this device for access to historical messages"); + await expect(tile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); + } + + // Now verify our device (setting up key backup), and check what happens + await verifySession(app, secretStorageKey); + const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2); + + // The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though. + await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message"); + await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); + + // The second message should now be decrypted, with a grey shield + await expect(tilesAfterVerify[1]).toContainText("test2 test2"); + await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon_normal")).toBeVisible(); + }); + + test.describe("non-joined historical messages", () => { + test.skip(isDendrite, "does not yet support membership on events"); + + test("should display undecryptable non-joined historical messages with a different message", async ({ + homeserver, + page, + app, + credentials: aliceCredentials, + user: alice, + bot: bob, + }) => { + // Bob creates an encrypted room and sends a message to it. He then invites Alice + const roomId = await bob.evaluate( + async (client, { alice }) => { + const encryptionStatePromise = new Promise((resolve) => { + client.on("RoomState.events" as EmittedEvents, (event, _state, _lastStateEvent) => { + if (event.getType() === "m.room.encryption") { + resolve(); + } + }); + }); + + const { room_id: roomId } = await client.createRoom({ + initial_state: [ + { + type: "m.room.encryption", + content: { + algorithm: "m.megolm.v1.aes-sha2", + }, + }, + ], + name: "Test room", + preset: "private_chat" as Preset, + }); + + // wait for m.room.encryption event, so that when we send a + // message, it will be encrypted + await encryptionStatePromise; + + await client.sendTextMessage(roomId, "This should be undecryptable"); + + await client.invite(roomId, alice.userId); + + return roomId; + }, + { alice }, + ); + + // Alice accepts the invite + await expect( + page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"), + ).toHaveCount(1); + await page.getByRole("treeitem", { name: "Test room" }).click(); + await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); + + // Bob sends an encrypted event and an undecryptable event + await bob.evaluate( + async (client, { roomId }) => { + await client.sendTextMessage(roomId, "This should be decryptable"); + await client.sendEvent( + roomId, + "m.room.encrypted" as any, + { + algorithm: "m.megolm.v1.aes-sha2", + ciphertext: "this+message+will+be+undecryptable", + device_id: client.getDeviceId()!, + sender_key: (await client.getCrypto()!.getOwnDeviceKeys()).ed25519, + session_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + } as any, + ); + }, + { roomId }, + ); + + // We wait for the event tiles that we expect from the messages that + // Bob sent, in sequence. + await expect( + page.locator(`.mx_EventTile`).getByText("You don't have access to this message"), + ).toBeVisible(); + await expect(page.locator(`.mx_EventTile`).getByText("This should be decryptable")).toBeVisible(); + await expect(page.locator(`.mx_EventTile`).getByText("Unable to decrypt message")).toBeVisible(); + + // And then we ensure that they are where we expect them to be + // Alice should see these event tiles: + // - first message sent by Bob (undecryptable) + // - Bob invited Alice + // - Alice joined the room + // - second message sent by Bob (decryptable) + // - third message sent by Bob (undecryptable) + const tiles = await page.locator(".mx_EventTile").all(); + expect(tiles.length).toBeGreaterThanOrEqual(5); + + // The first message from Bob was sent before Alice was in the room, so should + // be different from the standard UTD message + await expect(tiles[tiles.length - 5]).toContainText("You don't have access to this message"); + await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); + + // The second message from Bob should be decryptable + await expect(tiles[tiles.length - 2]).toContainText("This should be decryptable"); + // this tile won't have an e2e icon since we got the key from the sender + + // The third message from Bob is undecryptable, but was sent while Alice was + // in the room and is expected to be decryptable, so this should have the + // standard UTD message + await expect(tiles[tiles.length - 1]).toContainText("Unable to decrypt message"); + await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); + }); + + test("should be able to jump to a message sent before our last join event", async ({ + homeserver, + page, + app, + credentials: aliceCredentials, + user: alice, + bot: bob, + }) => { + // Bob: + // - creates an encrypted room, + // - invites Alice, + // - sends a message to it, + // - kicks Alice, + // - sends a bunch more events + // - invites Alice again + // In this way, there will be an event that Alice can decrypt, + // followed by a bunch of undecryptable events which Alice shouldn't + // expect to be able to decrypt. The old code would have hidden all + // the events, even the decryptable event (which it wouldn't have + // even tried to fetch, if it was far enough back). + const { roomId, eventId } = await bob.evaluate( + async (client, { alice }) => { + const { room_id: roomId } = await client.createRoom({ + initial_state: [ + { + type: "m.room.encryption", + content: { + algorithm: "m.megolm.v1.aes-sha2", + }, + }, + ], + name: "Test room", + preset: "private_chat" as Preset, + }); + + // invite Alice + const inviteAlicePromise = new Promise((resolve) => { + client.on("RoomMember.membership" as EmittedEvents, (_event, member, _oldMembership?) => { + if (member.userId === alice.userId && member.membership === "invite") { + resolve(); + } + }); + }); + await client.invite(roomId, alice.userId); + // wait for the invite to come back so that we encrypt to Alice + await inviteAlicePromise; + + // send a message that Alice should be able to decrypt + const { event_id: eventId } = await client.sendTextMessage( + roomId, + "This should be decryptable", + ); + + // kick Alice + const kickAlicePromise = new Promise((resolve) => { + client.on("RoomMember.membership" as EmittedEvents, (_event, member, _oldMembership?) => { + if (member.userId === alice.userId && member.membership === "leave") { + resolve(); + } + }); + }); + await client.kick(roomId, alice.userId); + await kickAlicePromise; + + // send a bunch of messages that Alice won't be able to decrypt + for (let i = 0; i < 20; i++) { + await client.sendTextMessage(roomId, `${i}`); + } + + // invite Alice again + await client.invite(roomId, alice.userId); + + return { roomId, eventId }; + }, + { alice }, + ); + + // Alice accepts the invite + await expect( + page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"), + ).toHaveCount(1); + await page.getByRole("treeitem", { name: "Test room" }).click(); + await page.locator(".mx_RoomView").getByRole("button", { name: "Accept" }).click(); + + // wait until we're joined and see the timeline + await expect(page.locator(`.mx_EventTile`).getByText("Alice joined the room")).toBeVisible(); + + // we should be able to jump to the decryptable message that Bob sent + await page.goto(`#/room/${roomId}/${eventId}`); + + await expect(page.locator(`.mx_EventTile`).getByText("This should be decryptable")).toBeVisible(); + }); + }); + }); +}); diff --git a/playwright/e2e/crypto/dehydration.spec.ts b/playwright/e2e/crypto/dehydration.spec.ts index 13da99cca..eb9efde4e 100644 --- a/playwright/e2e/crypto/dehydration.spec.ts +++ b/playwright/e2e/crypto/dehydration.spec.ts @@ -102,7 +102,7 @@ test.describe("Dehydration", () => { await viewRoomSummaryByName(page, app, ROOM_NAME); - await page.getByRole("menuitem", { name: "People" }).click(); + await page.locator(".mx_RightPanelTabs").getByText("People").click(); await expect(page.locator(".mx_MemberList")).toBeVisible(); await getMemberTileByName(page, NAME).click(); diff --git a/playwright/e2e/crypto/verification.spec.ts b/playwright/e2e/crypto/device-verification.spec.ts similarity index 64% rename from playwright/e2e/crypto/verification.spec.ts rename to playwright/e2e/crypto/device-verification.spec.ts index e471b6b2f..929da0910 100644 --- a/playwright/e2e/crypto/verification.spec.ts +++ b/playwright/e2e/crypto/device-verification.spec.ts @@ -15,19 +15,18 @@ limitations under the License. */ import jsQR from "jsqr"; -import { type Preset, type Visibility } from "matrix-js-sdk/src/matrix"; import type { JSHandle, Locator, Page } from "@playwright/test"; -import type { VerificationRequest, Verifier } from "matrix-js-sdk/src/crypto-api"; +import type { VerificationRequest } from "matrix-js-sdk/src/crypto-api"; import { test, expect } from "../../element-web-test"; import { + awaitVerifier, checkDeviceIsConnectedKeyBackup, checkDeviceIsCrossSigned, doTwoWaySasVerification, logIntoElement, waitForVerificationRequest, } from "./utils"; -import { Client } from "../../pages/client"; import { Bot } from "../../pages/bot"; test.describe("Device verification", () => { @@ -40,25 +39,26 @@ test.describe("Device verification", () => { // Visit the login page of the app, to load the matrix sdk await page.goto("/#/login"); - await page.pause(); - // wait for the page to load await page.waitForSelector(".mx_AuthPage", { timeout: 30000 }); // Create a new device for alice aliceBotClient = new Bot(page, homeserver, { - rustCrypto: true, bootstrapCrossSigning: true, bootstrapSecretStorage: true, }); aliceBotClient.setCredentials(credentials); - const mxClientHandle = await aliceBotClient.prepareClient(); - - await page.waitForTimeout(20000); - expectedBackupVersion = await mxClientHandle.evaluate(async (mxClient) => { - return await mxClient.getCrypto()!.getActiveSessionBackupVersion(); - }); + // Backup is prepared in the background. Poll until it is ready. + const botClientHandle = await aliceBotClient.prepareClient(); + await expect + .poll(async () => { + expectedBackupVersion = await botClientHandle.evaluate((cli) => + cli.getCrypto()!.getActiveSessionBackupVersion(), + ); + return expectedBackupVersion; + }) + .not.toBe(null); }); // Click the "Verify with another device" button, and have the bot client auto-accept it. @@ -234,115 +234,6 @@ test.describe("Device verification", () => { }); }); -test.describe("User verification", () => { - // note that there are other tests that check user verification works in `crypto.spec.ts`. - - test.use({ - displayName: "Alice", - botCreateOpts: { displayName: "Bob", autoAcceptInvites: true, userIdPrefix: "bob_" }, - room: async ({ page, app, bot: bob, user: aliceCredentials }, use) => { - await app.client.bootstrapCrossSigning(aliceCredentials); - - // the other user creates a DM - const dmRoomId = await createDMRoom(bob, aliceCredentials.userId); - - // accept the DM - await app.viewRoomByName("Bob"); - await page.getByRole("button", { name: "Start chatting" }).click(); - await use({ roomId: dmRoomId }); - }, - }); - - test("can receive a verification request when there is no existing DM", async ({ - page, - bot: bob, - user: aliceCredentials, - toasts, - room: { roomId: dmRoomId }, - }) => { - // once Alice has joined, Bob starts the verification - const bobVerificationRequest = await bob.evaluateHandle( - async (client, { dmRoomId, aliceCredentials }) => { - const room = client.getRoom(dmRoomId); - while (room.getMember(aliceCredentials.userId)?.membership !== "join") { - await new Promise((resolve) => { - room.once(window.matrixcs.RoomStateEvent.Members, resolve); - }); - } - - return client.getCrypto().requestVerificationDM(aliceCredentials.userId, dmRoomId); - }, - { dmRoomId, aliceCredentials }, - ); - - // there should also be a toast - const toast = await toasts.getToast("Verification requested"); - // it should contain the details of the requesting user - await expect(toast.getByText(`Bob (${bob.credentials.userId})`)).toBeVisible(); - // Accept - await toast.getByRole("button", { name: "Verify Session" }).click(); - - // request verification by emoji - await page.locator("#mx_RightPanel").getByRole("button", { name: "Verify by emoji" }).click(); - - /* on the bot side, wait for the verifier to exist ... */ - const botVerifier = await awaitVerifier(bobVerificationRequest); - // ... confirm ... - botVerifier.evaluate((verifier) => verifier.verify()); - // ... and then check the emoji match - await doTwoWaySasVerification(page, botVerifier); - - await page.getByRole("button", { name: "They match" }).click(); - await expect(page.getByText("You've successfully verified Bob!")).toBeVisible(); - await page.getByRole("button", { name: "Got it" }).click(); - }); - - test("can abort emoji verification when emoji mismatch", async ({ - page, - bot: bob, - user: aliceCredentials, - toasts, - room: { roomId: dmRoomId }, - cryptoBackend, - }) => { - test.skip(cryptoBackend === "legacy", "Not implemented for legacy crypto"); - - // once Alice has joined, Bob starts the verification - const bobVerificationRequest = await bob.evaluateHandle( - async (client, { dmRoomId, aliceCredentials }) => { - const room = client.getRoom(dmRoomId); - while (room.getMember(aliceCredentials.userId)?.membership !== "join") { - await new Promise((resolve) => { - room.once(window.matrixcs.RoomStateEvent.Members, resolve); - }); - } - - return client.getCrypto().requestVerificationDM(aliceCredentials.userId, dmRoomId); - }, - { dmRoomId, aliceCredentials }, - ); - - // Accept verification via toast - const toast = await toasts.getToast("Verification requested"); - await toast.getByRole("button", { name: "Verify Session" }).click(); - - // request verification by emoji - await page.locator("#mx_RightPanel").getByRole("button", { name: "Verify by emoji" }).click(); - - /* on the bot side, wait for the verifier to exist ... */ - const botVerifier = await awaitVerifier(bobVerificationRequest); - // ... confirm ... - botVerifier.evaluate((verifier) => verifier.verify()).catch(() => {}); - // ... and abort the verification - await page.getByRole("button", { name: "They don't match" }).click(); - - const dialog = page.locator(".mx_Dialog"); - await expect(dialog.getByText("Your messages are not secure")).toBeVisible(); - await dialog.getByRole("button", { name: "OK" }).click(); - await expect(dialog).not.toBeVisible(); - }); -}); - /** Extract the qrcode out of an on-screen html element */ async function readQrCode(base: Locator) { const qrCode = base.locator('[alt="QR Code"]'); @@ -374,35 +265,3 @@ async function readQrCode(base: Locator) { const result = jsQR(new Uint8ClampedArray(imageData.buffer), imageData.width, imageData.height); return new Uint8Array(result.binaryData); } - -async function createDMRoom(client: Client, userId: string): Promise { - return client.createRoom({ - preset: "trusted_private_chat" as Preset, - visibility: "private" as Visibility, - invite: [userId], - is_direct: true, - initial_state: [ - { - type: "m.room.encryption", - state_key: "", - content: { - algorithm: "m.megolm.v1.aes-sha2", - }, - }, - ], - }); -} - -/** - * Wait for a verifier to exist for a VerificationRequest - * - * @param botVerificationRequest - */ -async function awaitVerifier(botVerificationRequest: JSHandle): Promise> { - return botVerificationRequest.evaluateHandle(async (verificationRequest) => { - while (!verificationRequest.verifier) { - await new Promise((r) => verificationRequest.once("change" as any, r)); - } - return verificationRequest.verifier; - }); -} diff --git a/playwright/e2e/crypto/event-shields.spec.ts b/playwright/e2e/crypto/event-shields.spec.ts new file mode 100644 index 000000000..b242dd060 --- /dev/null +++ b/playwright/e2e/crypto/event-shields.spec.ts @@ -0,0 +1,269 @@ +/* +Copyright 2022-2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { expect, test } from "../../element-web-test"; +import { autoJoin, createSharedRoomWithUser, enableKeyBackup, logIntoElement, logOutOfElement, verify } from "./utils"; +import { Bot } from "../../pages/bot"; + +test.describe("Cryptography", function () { + test.use({ + displayName: "Alice", + botCreateOpts: { + displayName: "Bob", + autoAcceptInvites: false, + }, + }); + + test.describe("event shields", () => { + let testRoomId: string; + + test.beforeEach(async ({ page, bot: bob, user: aliceCredentials, app }) => { + await app.client.bootstrapCrossSigning(aliceCredentials); + await autoJoin(bob); + + // create an encrypted room + testRoomId = await createSharedRoomWithUser(app, bob.credentials.userId, { + name: "TestRoom", + initial_state: [ + { + type: "m.room.encryption", + state_key: "", + content: { + algorithm: "m.megolm.v1.aes-sha2", + }, + }, + ], + }); + }); + + test("should show the correct shield on e2e events", async ({ page, app, bot: bob, homeserver }) => { + // Bob has a second, not cross-signed, device + const bobSecondDevice = new Bot(page, homeserver, { + bootstrapSecretStorage: false, + bootstrapCrossSigning: false, + }); + bobSecondDevice.setCredentials( + await homeserver.loginUser(bob.credentials.userId, bob.credentials.password), + ); + await bobSecondDevice.prepareClient(); + + await bob.sendEvent(testRoomId, null, "m.room.encrypted", { + algorithm: "m.megolm.v1.aes-sha2", + ciphertext: "the bird is in the hand", + }); + + const last = page.locator(".mx_EventTile_last"); + await expect(last).toContainText("Unable to decrypt message"); + const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); + await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/); + await lastE2eIcon.focus(); + await expect(page.getByRole("tooltip")).toContainText("This message could not be decrypted"); + + /* Should show a red padlock for an unencrypted message in an e2e room */ + await bob.evaluate( + (cli, testRoomId) => + cli.http.authedRequest( + window.matrixcs.Method.Put, + `/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`, + undefined, + { + msgtype: "m.text", + body: "test unencrypted", + }, + ), + testRoomId, + ); + + await expect(last).toContainText("test unencrypted"); + await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); + await lastE2eIcon.focus(); + await expect(page.getByRole("tooltip")).toContainText("Not encrypted"); + + /* Should show no padlock for an unverified user */ + // bob sends a valid event + await bob.sendMessage(testRoomId, "test encrypted 1"); + + // the message should appear, decrypted, with no warning, but also no "verified" + const lastTile = page.locator(".mx_EventTile_last"); + const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); + await expect(lastTile).toContainText("test encrypted 1"); + // no e2e icon + await expect(lastTileE2eIcon).not.toBeVisible(); + + /* Now verify Bob */ + await verify(app, bob); + + /* Existing message should be updated when user is verified. */ + await expect(last).toContainText("test encrypted 1"); + // still no e2e icon + await expect(last.locator(".mx_EventTile_e2eIcon")).not.toBeVisible(); + + /* should show no padlock, and be verified, for a message from a verified device */ + await bob.sendMessage(testRoomId, "test encrypted 2"); + + await expect(lastTile).toContainText("test encrypted 2"); + // no e2e icon + await expect(lastTileE2eIcon).not.toBeVisible(); + + /* should show red padlock for a message from an unverified device */ + await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); + await expect(lastTile).toContainText("test encrypted from unverified"); + await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); + await lastTileE2eIcon.focus(); + await expect(page.getByRole("tooltip")).toContainText("Encrypted by a device not verified by its owner."); + + /* Should show a grey padlock for a message from an unknown device */ + // bob deletes his second device + await bobSecondDevice.evaluate((cli) => cli.logout(true)); + + // wait for the logout to propagate. Workaround for https://github.com/vector-im/element-web/issues/26263 by repeatedly closing and reopening Bob's user info. + async function awaitOneDevice(iterations = 1) { + const rightPanel = page.locator(".mx_RightPanel"); + await rightPanel.getByTestId("base-card-back-button").click(); + await rightPanel.getByText("Bob").click(); + const sessionCountText = await rightPanel + .locator(".mx_UserInfo_devices") + .getByText(" session", { exact: false }) + .textContent(); + // cf https://github.com/vector-im/element-web/issues/26279: Element-R uses the wrong text here + if (sessionCountText != "1 session" && sessionCountText != "1 verified session") { + if (iterations >= 10) { + throw new Error(`Bob still has ${sessionCountText} after 10 iterations`); + } + await awaitOneDevice(iterations + 1); + } + } + + await awaitOneDevice(); + + // close and reopen the room, to get the shield to update. + await app.viewRoomByName("Bob"); + await app.viewRoomByName("TestRoom"); + + await expect(last).toContainText("test encrypted from unverified"); + await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); + await lastE2eIcon.focus(); + await expect(page.getByRole("tooltip")).toContainText("Encrypted by an unknown or deleted device."); + }); + + test("Should show a grey padlock for a key restored from backup", async ({ + page, + app, + bot: bob, + homeserver, + user: aliceCredentials, + }) => { + test.slow(); + const securityKey = await enableKeyBackup(app); + + // bob sends a valid event + await bob.sendMessage(testRoomId, "test encrypted 1"); + + const lastTile = page.locator(".mx_EventTile_last"); + const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); + await expect(lastTile).toContainText("test encrypted 1"); + // no e2e icon + await expect(lastTileE2eIcon).not.toBeVisible(); + + // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for + // the key to be backed up. + await page.waitForTimeout(10000); + + /* log out, and back in */ + await logOutOfElement(page); + // Reload to work around a Rust crypto bug where it can hold onto the indexeddb even after logout + // https://github.com/element-hq/element-web/issues/25779 + await page.addInitScript(() => { + // When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures + // will re-inject the original credentials into localStorage, which we don't want. + // To work around, we add a second initScript which will clear localStorage again. + window.localStorage.clear(); + }); + await page.reload(); + await logIntoElement(page, homeserver, aliceCredentials, securityKey); + + /* go back to the test room and find Bob's message again */ + await app.viewRoomById(testRoomId); + await expect(lastTile).toContainText("test encrypted 1"); + // The gray shield would be a mx_EventTile_e2eIcon_normal. The red shield would be a mx_EventTile_e2eIcon_warning. + // No shield would have no div mx_EventTile_e2eIcon at all. + await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_normal/); + await lastTileE2eIcon.hover(); + // The key is coming from backup, so it is not anymore possible to establish if the claimed device + // creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device." + // It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted. + await expect(page.getByRole("tooltip")).toContainText( + "The authenticity of this encrypted message can't be guaranteed on this device.", + ); + }); + + test("should show the correct shield on edited e2e events", async ({ page, app, bot: bob, homeserver }) => { + // bob has a second, not cross-signed, device + const bobSecondDevice = new Bot(page, homeserver, { + bootstrapSecretStorage: false, + bootstrapCrossSigning: false, + }); + bobSecondDevice.setCredentials( + await homeserver.loginUser(bob.credentials.userId, bob.credentials.password), + ); + await bobSecondDevice.prepareClient(); + + // verify Bob + await verify(app, bob); + + // bob sends a valid event + const testEvent = await bob.sendMessage(testRoomId, "Hoo!"); + + // the message should appear, decrypted, with no warning + await expect( + page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"), + ).not.toBeVisible(); + + // bob sends an edit to the first message with his unverified device + await bobSecondDevice.sendMessage(testRoomId, { + "m.new_content": { + msgtype: "m.text", + body: "Haa!", + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: testEvent.event_id, + }, + }); + + // the edit should have a warning + await expect( + page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon_warning"), + ).toBeVisible(); + + // a second edit from the verified device should be ok + await bob.sendMessage(testRoomId, { + "m.new_content": { + msgtype: "m.text", + body: "Hee!", + }, + "m.relates_to": { + rel_type: "m.replace", + event_id: testEvent.event_id, + }, + }); + + await expect( + page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon_warning"), + ).not.toBeVisible(); + }); + }); +}); diff --git a/playwright/e2e/crypto/staged-rollout.spec.ts b/playwright/e2e/crypto/staged-rollout.spec.ts deleted file mode 100644 index acdd20bc8..000000000 --- a/playwright/e2e/crypto/staged-rollout.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -/* -Copyright 2024 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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 { test, expect } from "../../element-web-test"; -import { createRoom, enableKeyBackup, logIntoElement, logOutOfElement, sendMessageInCurrentRoom } from "./utils"; -import { SettingLevel } from "../../../src/settings/SettingLevel"; - -test.describe("Adoption of rust stack", () => { - test("Test migration of existing logins when rollout is 100%", async ({ - page, - context, - app, - credentials, - homeserver, - }, workerInfo) => { - test.skip( - workerInfo.project.name === "Rust Crypto", - "No need to test this on Rust Crypto as we override the config manually", - ); - await page.goto("/#/login"); - test.slow(); - - let featureRustCrypto = false; - let stagedRolloutPercent = 0; - - await context.route(`http://localhost:8080/config.json*`, async (route) => { - const json = { - default_server_config: { - "m.homeserver": { - base_url: "https://server.invalid", - }, - }, - }; - json["features"] = { - feature_rust_crypto: featureRustCrypto, - }; - json["setting_defaults"] = { - "language": "en-GB", - "RustCrypto.staged_rollout_percent": stagedRolloutPercent, - }; - await route.fulfill({ json }); - }); - - // reload to ensure we read the config - await page.reload(); - - await logIntoElement(page, homeserver, credentials); - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Olm")).toBeVisible(); - - featureRustCrypto = true; - - await page.reload(); - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Olm")).toBeVisible(); - - stagedRolloutPercent = 100; - - await page.reload(); - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Rust SDK")).toBeVisible(); - }); - - test("Test new logins by default on rust stack", async ({ - page, - context, - app, - credentials, - homeserver, - }, workerInfo) => { - test.skip( - workerInfo.project.name === "Rust Crypto", - "No need to test this on Rust Crypto as we override the config manually", - ); - test.slow(); - await page.goto("/#/login"); - - await context.route(`http://localhost:8080/config.json*`, async (route) => { - const json = { - default_server_config: { - "m.homeserver": { - base_url: "https://server.invalid", - }, - }, - }; - // we only want to test the default - json["features"] = {}; - json["setting_defaults"] = { - language: "en-GB", - }; - await route.fulfill({ json }); - }); - - // reload to get the new config - await page.reload(); - await logIntoElement(page, homeserver, credentials); - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Rust SDK")).toBeVisible(); - }); - - test("Test default is to not rollout existing logins", async ({ - page, - context, - app, - credentials, - homeserver, - }, workerInfo) => { - test.skip( - workerInfo.project.name === "Rust Crypto", - "No need to test this on Rust Crypto as we override the config manually", - ); - test.slow(); - - await page.goto("/#/login"); - - // In the project.name = "Legacy crypto" it will be olm crypto - await logIntoElement(page, homeserver, credentials); - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Olm")).toBeVisible(); - - // Now simulate a refresh with `feature_rust_crypto` enabled but ensure we use the default rollout - await context.route(`http://localhost:8080/config.json*`, async (route) => { - const json = {}; - json["features"] = { - feature_rust_crypto: true, - }; - json["setting_defaults"] = { - // We want to test the default so we don't set this - // "RustCrypto.staged_rollout_percent": 0, - }; - await route.fulfill({ json }); - }); - - await page.reload(); - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Olm")).toBeVisible(); - }); - - test("Migrate using labflag should work", async ({ page, context, app, credentials, homeserver }, workerInfo) => { - test.skip( - workerInfo.project.name === "Rust Crypto", - "No need to test this on Rust Crypto as we override the config manually", - ); - test.slow(); - - await page.goto("/#/login"); - - // In the project.name = "Legacy crypto" it will be olm crypto - await logIntoElement(page, homeserver, credentials); - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Olm")).toBeVisible(); - - // We need to enable devtools for this test - await app.settings.setValue("developerMode", null, SettingLevel.ACCOUNT, true); - - // Now simulate a refresh with `feature_rust_crypto` enabled but ensure no automatic migration - await context.route(`http://localhost:8080/config.json*`, async (route) => { - const json = {}; - json["features"] = { - feature_rust_crypto: true, - }; - json["setting_defaults"] = { - "RustCrypto.staged_rollout_percent": 0, - }; - await route.fulfill({ json }); - }); - - await page.reload(); - - // Go to the labs flag and enable the migration - await app.settings.openUserSettings("Labs"); - await page.getByRole("switch", { name: "Rust cryptography implementation" }).click(); - - // Fixes a bug where a missing session data was shown - // https://github.com/element-hq/element-web/issues/26970 - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Rust SDK")).toBeVisible(); - }); - - test("Test migration of room shields", async ({ page, context, app, credentials, homeserver }, workerInfo) => { - test.skip( - workerInfo.project.name === "Rust Crypto", - "No need to test this on Rust Crypto as we override the config manually", - ); - test.slow(); - - await page.goto("/#/login"); - - // In the project.name = "Legacy crypto" it will be olm crypto - await logIntoElement(page, homeserver, credentials); - - // create a room and send a message - await createRoom(page, "Room1", true); - await sendMessageInCurrentRoom(page, "Hello"); - - // enable backup to save this room key - const securityKey = await enableKeyBackup(app); - - // wait a bit for upload to complete, there is a random timout on key upload - await page.waitForTimeout(6000); - - // logout - await logOutOfElement(page); - - // We logout and log back in in order to get the historical key from backup and have a gray shield - await page.reload(); - await page.goto("/#/login"); - // login again and verify - await logIntoElement(page, homeserver, credentials, securityKey); - - await app.viewRoomByName("Room1"); - - { - const messageDiv = page.locator(".mx_EventTile_line").filter({ hasText: "Hello" }); - // there should be a shield - await expect(messageDiv.locator(".mx_EventTile_e2eIcon")).toBeVisible(); - } - - // Now type a new message - await sendMessageInCurrentRoom(page, "World"); - - // wait a bit for the message to be sent - await expect( - page - .locator(".mx_EventTile_line") - .filter({ hasText: "World" }) - .locator("..") - .locator(".mx_EventTile_receiptSent"), - ).toBeVisible(); - { - const messageDiv = page.locator(".mx_EventTile_line").filter({ hasText: "World" }); - // there should not be a shield - expect(await messageDiv.locator(".mx_EventTile_e2eIcon").count()).toEqual(0); - } - - // trigger a migration - await context.route(`http://localhost:8080/config.json*`, async (route) => { - const json = {}; - json["features"] = { - feature_rust_crypto: true, - }; - json["setting_defaults"] = { - "RustCrypto.staged_rollout_percent": 100, - }; - await route.fulfill({ json }); - }); - - await page.reload(); - - await app.viewRoomByName("Room1"); - - // The shields should be migrated properly - { - const messageDiv = page.locator(".mx_EventTile_line").filter({ hasText: "Hello" }); - await expect(messageDiv).toBeVisible(); - // there should be a shield - await expect(messageDiv.locator(".mx_EventTile_e2eIcon")).toBeVisible(); - } - { - const messageDiv = page.locator(".mx_EventTile_line").filter({ hasText: "World" }); - await expect(messageDiv).toBeVisible(); - // there should not be a shield - expect(await messageDiv.locator(".mx_EventTile_e2eIcon").count()).toEqual(0); - } - - await app.settings.openUserSettings("Help & About"); - await expect(page.getByText("Crypto version: Rust SDK")).toBeVisible(); - }); -}); diff --git a/playwright/e2e/crypto/user-verification.spec.ts b/playwright/e2e/crypto/user-verification.spec.ts new file mode 100644 index 000000000..eac0fb639 --- /dev/null +++ b/playwright/e2e/crypto/user-verification.spec.ts @@ -0,0 +1,145 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { type Preset, type Visibility } from "matrix-js-sdk/src/matrix"; + +import { test, expect } from "../../element-web-test"; +import { doTwoWaySasVerification, awaitVerifier } from "./utils"; +import { Client } from "../../pages/client"; + +test.describe("User verification", () => { + // note that there are other tests that check user verification works in `crypto.spec.ts`. + + test.use({ + displayName: "Alice", + botCreateOpts: { displayName: "Bob", autoAcceptInvites: true, userIdPrefix: "bob_" }, + room: async ({ page, app, bot: bob, user: aliceCredentials }, use) => { + await app.client.bootstrapCrossSigning(aliceCredentials); + + // the other user creates a DM + const dmRoomId = await createDMRoom(bob, aliceCredentials.userId); + + // accept the DM + await app.viewRoomByName("Bob"); + await page.getByRole("button", { name: "Start chatting" }).click(); + await use({ roomId: dmRoomId }); + }, + }); + + test("can receive a verification request when there is no existing DM", async ({ + page, + bot: bob, + user: aliceCredentials, + toasts, + room: { roomId: dmRoomId }, + }) => { + // once Alice has joined, Bob starts the verification + const bobVerificationRequest = await bob.evaluateHandle( + async (client, { dmRoomId, aliceCredentials }) => { + const room = client.getRoom(dmRoomId); + while (room.getMember(aliceCredentials.userId)?.membership !== "join") { + await new Promise((resolve) => { + room.once(window.matrixcs.RoomStateEvent.Members, resolve); + }); + } + + return client.getCrypto().requestVerificationDM(aliceCredentials.userId, dmRoomId); + }, + { dmRoomId, aliceCredentials }, + ); + + // there should also be a toast + const toast = await toasts.getToast("Verification requested"); + // it should contain the details of the requesting user + await expect(toast.getByText(`Bob (${bob.credentials.userId})`)).toBeVisible(); + // Accept + await toast.getByRole("button", { name: "Verify User" }).click(); + + // request verification by emoji + await page.locator("#mx_RightPanel").getByRole("button", { name: "Verify by emoji" }).click(); + + /* on the bot side, wait for the verifier to exist ... */ + const botVerifier = await awaitVerifier(bobVerificationRequest); + // ... confirm ... + botVerifier.evaluate((verifier) => verifier.verify()); + // ... and then check the emoji match + await doTwoWaySasVerification(page, botVerifier); + + await page.getByRole("button", { name: "They match" }).click(); + await expect(page.getByText("You've successfully verified Bob!")).toBeVisible(); + await page.getByRole("button", { name: "Got it" }).click(); + }); + + test("can abort emoji verification when emoji mismatch", async ({ + page, + bot: bob, + user: aliceCredentials, + toasts, + room: { roomId: dmRoomId }, + }) => { + // once Alice has joined, Bob starts the verification + const bobVerificationRequest = await bob.evaluateHandle( + async (client, { dmRoomId, aliceCredentials }) => { + const room = client.getRoom(dmRoomId); + while (room.getMember(aliceCredentials.userId)?.membership !== "join") { + await new Promise((resolve) => { + room.once(window.matrixcs.RoomStateEvent.Members, resolve); + }); + } + + return client.getCrypto().requestVerificationDM(aliceCredentials.userId, dmRoomId); + }, + { dmRoomId, aliceCredentials }, + ); + + // Accept verification via toast + const toast = await toasts.getToast("Verification requested"); + await toast.getByRole("button", { name: "Verify User" }).click(); + + // request verification by emoji + await page.locator("#mx_RightPanel").getByRole("button", { name: "Verify by emoji" }).click(); + + /* on the bot side, wait for the verifier to exist ... */ + const botVerifier = await awaitVerifier(bobVerificationRequest); + // ... confirm ... + botVerifier.evaluate((verifier) => verifier.verify()).catch(() => {}); + // ... and abort the verification + await page.getByRole("button", { name: "They don't match" }).click(); + + const dialog = page.locator(".mx_Dialog"); + await expect(dialog.getByText("Your messages are not secure")).toBeVisible(); + await dialog.getByRole("button", { name: "OK" }).click(); + await expect(dialog).not.toBeVisible(); + }); +}); + +async function createDMRoom(client: Client, userId: string): Promise { + return client.createRoom({ + preset: "trusted_private_chat" as Preset, + visibility: "private" as Visibility, + invite: [userId], + is_direct: true, + initial_state: [ + { + type: "m.room.encryption", + state_key: "", + content: { + algorithm: "m.megolm.v1.aes-sha2", + }, + }, + ], + }); +} diff --git a/playwright/e2e/crypto/utils.ts b/playwright/e2e/crypto/utils.ts index 5b0bf29b9..3c1e26711 100644 --- a/playwright/e2e/crypto/utils.ts +++ b/playwright/e2e/crypto/utils.ts @@ -27,6 +27,7 @@ import type { import { Credentials, HomeserverInstance } from "../../plugins/homeserver"; import { Client } from "../../pages/client"; import { ElementAppPage } from "../../pages/ElementAppPage"; +import { Bot } from "../../pages/bot"; /** * wait for the given client to receive an incoming verification request, and automatically accept it @@ -113,6 +114,13 @@ export async function checkDeviceIsConnectedKeyBackup( expectedBackupVersion: string, checkBackupKeyInCache: boolean, ): Promise { + // Sanity check the given backup version: if it's null, something went wrong earlier in the test. + if (!expectedBackupVersion) { + throw new Error( + `Invalid backup version passed to \`checkDeviceIsConnectedKeyBackup\`: ${expectedBackupVersion}`, + ); + } + await page.getByRole("button", { name: "User menu" }).click(); await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Security & Privacy" }).click(); await expect(page.locator(".mx_Dialog").getByRole("button", { name: "Restore from Backup" })).toBeVisible(); @@ -320,3 +328,60 @@ export async function createRoom(page: Page, roomName: string, isEncrypted: bool await expect(page.getByText("Encryption enabled")).toBeVisible(); } } + +/** + * Configure the given MatrixClient to auto-accept any invites + * @param client - the client to configure + */ +export async function autoJoin(client: Client) { + await client.evaluate((cli) => { + cli.on(window.matrixcs.RoomMemberEvent.Membership, (event, member) => { + if (member.membership === "invite" && member.userId === cli.getUserId()) { + cli.joinRoom(member.roomId); + } + }); + }); +} + +/** + * Verify a user by emoji + * @param page - the page to use + * @param bob - the user to verify + */ +export const verify = async (app: ElementAppPage, bob: Bot) => { + const page = app.page; + const bobsVerificationRequestPromise = waitForVerificationRequest(bob); + + const roomInfo = await app.toggleRoomInfoPanel(); + await page.locator(".mx_RightPanelTabs").getByText("People").click(); + await roomInfo.getByText("Bob").click(); + await roomInfo.getByRole("button", { name: "Verify" }).click(); + await roomInfo.getByRole("button", { name: "Start Verification" }).click(); + + // this requires creating a DM, so can take a while. Give it a longer timeout. + await roomInfo.getByRole("button", { name: "Verify by emoji" }).click({ timeout: 30000 }); + + const request = await bobsVerificationRequestPromise; + // the bot user races with the Element user to hit the "verify by emoji" button + const verifier = await request.evaluateHandle((request) => request.startVerification("m.sas.v1")); + await doTwoWaySasVerification(page, verifier); + await roomInfo.getByRole("button", { name: "They match" }).click(); + await expect(roomInfo.getByText("You've successfully verified Bob!")).toBeVisible(); + await roomInfo.getByRole("button", { name: "Got it" }).click(); +}; + +/** + * Wait for a verifier to exist for a VerificationRequest + * + * @param botVerificationRequest + */ +export async function awaitVerifier( + botVerificationRequest: JSHandle, +): Promise> { + return botVerificationRequest.evaluateHandle(async (verificationRequest) => { + while (!verificationRequest.verifier) { + await new Promise((r) => verificationRequest.once("change" as any, r)); + } + return verificationRequest.verifier; + }); +} diff --git a/playwright/e2e/file-upload/image-upload.spec.ts b/playwright/e2e/file-upload/image-upload.spec.ts index 8f0403af3..d75d20f44 100644 --- a/playwright/e2e/file-upload/image-upload.spec.ts +++ b/playwright/e2e/file-upload/image-upload.spec.ts @@ -38,8 +38,8 @@ test.describe("Image Upload", () => { .locator(".mx_MessageComposer_actions input[type='file']") .setInputFiles("playwright/sample-files/riot.png"); - expect(page.getByRole("button", { name: "Upload" })).toBeEnabled(); - expect(page.getByRole("button", { name: "Close dialog" })).toBeEnabled(); - expect(page).toMatchScreenshot("image-upload-preview.png"); + await expect(page.getByRole("button", { name: "Upload" })).toBeEnabled(); + await expect(page.getByRole("button", { name: "Close dialog" })).toBeEnabled(); + await expect(page).toMatchScreenshot("image-upload-preview.png"); }); }); diff --git a/playwright/e2e/forgot-password/forgot-password.spec.ts b/playwright/e2e/forgot-password/forgot-password.spec.ts new file mode 100644 index 000000000..260242ebc --- /dev/null +++ b/playwright/e2e/forgot-password/forgot-password.spec.ts @@ -0,0 +1,77 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { expect, test } from "../../element-web-test"; +import { selectHomeserver } from "../utils"; + +const username = "user1234"; +// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen. +const password = "oETo7MPf0o"; +const email = "user@nowhere.dummy"; + +test.describe("Forgot Password", () => { + test.use({ + startHomeserverOpts: ({ mailhog }, use) => + use({ + template: "email", + variables: { + SMTP_HOST: "host.containers.internal", + SMTP_PORT: mailhog.instance.smtpPort, + }, + }), + }); + + test("renders properly", async ({ page, homeserver }) => { + await page.goto("/"); + + await page.getByRole("link", { name: "Sign in" }).click(); + + // need to select a homeserver at this stage, before entering the forgot password flow + await selectHomeserver(page, homeserver.config.baseUrl); + + await page.getByRole("button", { name: "Forgot password?" }).click(); + + await expect(page.getByRole("main")).toMatchScreenshot("forgot-password.png"); + }); + + test("renders email verification dialog properly", async ({ page, homeserver }) => { + const user = await homeserver.registerUser(username, password); + + await homeserver.setThreepid(user.userId, "email", email); + + await page.goto("/"); + + await page.getByRole("link", { name: "Sign in" }).click(); + await selectHomeserver(page, homeserver.config.baseUrl); + + await page.getByRole("button", { name: "Forgot password?" }).click(); + + await page.getByRole("textbox", { name: "Email address" }).fill(email); + + await page.getByRole("button", { name: "Send email" }).click(); + + await page.getByRole("button", { name: "Next" }).click(); + + await page.getByRole("textbox", { name: "New Password", exact: true }).fill(password); + await page.getByRole("textbox", { name: "Confirm new password", exact: true }).fill(password); + + await page.getByRole("button", { name: "Reset password" }).click(); + + await expect(page.getByRole("button", { name: "Resend" })).toBeInViewport(); + + await expect(page.locator(".mx_Dialog")).toMatchScreenshot("forgot-password-verify-email.png"); + }); +}); diff --git a/playwright/e2e/integration-manager/get-openid-token.spec.ts b/playwright/e2e/integration-manager/get-openid-token.spec.ts index c107bb2cb..a0f099cb6 100644 --- a/playwright/e2e/integration-manager/get-openid-token.spec.ts +++ b/playwright/e2e/integration-manager/get-openid-token.spec.ts @@ -118,8 +118,8 @@ test.describe("Integration Manager: Get OpenID Token", () => { await app.viewRoomByName(ROOM_NAME); }); - test("should successfully obtain an openID token", async ({ page }) => { - await openIntegrationManager(page); + test("should successfully obtain an openID token", async ({ page, app }) => { + await openIntegrationManager(app); await sendActionFromIntegrationManager(page, integrationManagerUrl); const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`); diff --git a/playwright/e2e/integration-manager/kick.spec.ts b/playwright/e2e/integration-manager/kick.spec.ts index b5ca6a1b3..afe2de0f1 100644 --- a/playwright/e2e/integration-manager/kick.spec.ts +++ b/playwright/e2e/integration-manager/kick.spec.ts @@ -167,7 +167,7 @@ test.describe("Integration Manager: Kick", () => { await app.client.inviteUser(room.roomId, targetUser.credentials.userId); await expect(page.getByText(`${BOT_DISPLAY_NAME} joined the room`)).toBeVisible(); - await openIntegrationManager(page); + await openIntegrationManager(app); await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId); await closeIntegrationManager(page, integrationManagerUrl); await expectKickedMessage(page, true); @@ -185,7 +185,7 @@ test.describe("Integration Manager: Kick", () => { }, }); - await openIntegrationManager(page); + await openIntegrationManager(app); await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId); await closeIntegrationManager(page, integrationManagerUrl); await expectKickedMessage(page, false); @@ -197,7 +197,7 @@ test.describe("Integration Manager: Kick", () => { await expect(page.getByText(`${BOT_DISPLAY_NAME} joined the room`)).toBeVisible(); await targetUser.leave(room.roomId); - await openIntegrationManager(page); + await openIntegrationManager(app); await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId); await closeIntegrationManager(page, integrationManagerUrl); await expectKickedMessage(page, false); @@ -209,7 +209,7 @@ test.describe("Integration Manager: Kick", () => { await expect(page.getByText(`${BOT_DISPLAY_NAME} joined the room`)).toBeVisible(); await app.client.ban(room.roomId, targetUser.credentials.userId); - await openIntegrationManager(page); + await openIntegrationManager(app); await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId); await closeIntegrationManager(page, integrationManagerUrl); await expectKickedMessage(page, false); @@ -218,7 +218,7 @@ test.describe("Integration Manager: Kick", () => { test("should no-op if the target was never a room member", async ({ page, app, bot: targetUser, room }) => { await app.viewRoomByName(ROOM_NAME); - await openIntegrationManager(page); + await openIntegrationManager(app); await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId); await closeIntegrationManager(page, integrationManagerUrl); await expectKickedMessage(page, false); diff --git a/playwright/e2e/integration-manager/read_events.spec.ts b/playwright/e2e/integration-manager/read_events.spec.ts index b17859667..2e2ee8d18 100644 --- a/playwright/e2e/integration-manager/read_events.spec.ts +++ b/playwright/e2e/integration-manager/read_events.spec.ts @@ -142,7 +142,7 @@ test.describe("Integration Manager: Read Events", () => { // Send a state event const sendEventResponse = await app.client.sendStateEvent(room.roomId, eventType, eventContent, stateKey); - await openIntegrationManager(page); + await openIntegrationManager(app); // Read state events await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, eventType, stateKey); @@ -162,7 +162,7 @@ test.describe("Integration Manager: Read Events", () => { // Send a state event const sendEventResponse = await app.client.sendStateEvent(room.roomId, eventType, eventContent, stateKey); - await openIntegrationManager(page); + await openIntegrationManager(app); // Read state events await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, eventType, stateKey); @@ -196,7 +196,7 @@ test.describe("Integration Manager: Read Events", () => { app.client.sendStateEvent(room.roomId, eventType, eventContent3, stateKey3), ]); - await openIntegrationManager(page); + await openIntegrationManager(app); // Read state events await sendActionFromIntegrationManager( @@ -217,11 +217,11 @@ test.describe("Integration Manager: Read Events", () => { await expect(iframe.locator("#message-response")).toContainText(`"content":${JSON.stringify(eventContent3)}`); }); - test("should fail to read an event type which is not allowed", async ({ page, room }) => { + test("should fail to read an event type which is not allowed", async ({ page, app, room }) => { const eventType = "com.example.event"; const stateKey = ""; - await openIntegrationManager(page); + await openIntegrationManager(app); // Read state events await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, eventType, stateKey); diff --git a/playwright/e2e/integration-manager/send_event.spec.ts b/playwright/e2e/integration-manager/send_event.spec.ts index 61bad8a3e..ea2c35530 100644 --- a/playwright/e2e/integration-manager/send_event.spec.ts +++ b/playwright/e2e/integration-manager/send_event.spec.ts @@ -137,7 +137,7 @@ test.describe("Integration Manager: Send Event", () => { ); await app.viewRoomByName(ROOM_NAME); - await openIntegrationManager(page); + await openIntegrationManager(app); }); test("should send a state event", async ({ page, app, room }) => { diff --git a/playwright/e2e/integration-manager/utils.ts b/playwright/e2e/integration-manager/utils.ts index 259ff732c..c6a2fb998 100644 --- a/playwright/e2e/integration-manager/utils.ts +++ b/playwright/e2e/integration-manager/utils.ts @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type { Page } from "@playwright/test"; +import type { ElementAppPage } from "../../pages/ElementAppPage"; -export async function openIntegrationManager(page: Page) { - await page.getByRole("button", { name: "Room info" }).click(); +export async function openIntegrationManager(app: ElementAppPage) { + const { page } = app; + await app.toggleRoomInfoPanel(); await page .locator(".mx_RoomSummaryCard_appsGroup") .getByRole("button", { name: "Add widgets, bridges & bots" }) diff --git a/playwright/e2e/invite/invite-dialog.spec.ts b/playwright/e2e/invite/invite-dialog.spec.ts index 98a57c8eb..d9e086aaa 100644 --- a/playwright/e2e/invite/invite-dialog.spec.ts +++ b/playwright/e2e/invite/invite-dialog.spec.ts @@ -36,7 +36,7 @@ test.describe("Invite dialog", function () { await expect(page.getByText("Hanako created and configured the room.")).toBeVisible(); // Open the room info panel - await page.getByRole("button", { name: "Room info" }).click(); + await app.toggleRoomInfoPanel(); await page.locator(".mx_BaseCard").getByRole("menuitem", { name: "Invite" }).click(); @@ -114,12 +114,9 @@ test.describe("Invite dialog", function () { // Assert that the hovered user name on invitation UI does not have background color // TODO: implement the test on room-header.spec.ts - const roomHeader = page.locator(".mx_LegacyRoomHeader"); - await roomHeader.locator(".mx_LegacyRoomHeader_name--textonly").hover(); - await expect(roomHeader.locator(".mx_LegacyRoomHeader_name--textonly")).toHaveCSS( - "background-color", - "rgba(0, 0, 0, 0)", - ); + const roomHeader = page.locator(".mx_RoomHeader"); + await roomHeader.locator(".mx_RoomHeader_heading").hover(); + await expect(roomHeader.locator(".mx_RoomHeader_heading")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)"); // Send a message to invite the bots const composer = app.getComposer().locator("[contenteditable]"); diff --git a/playwright/e2e/knock/create-knock-room.spec.ts b/playwright/e2e/knock/create-knock-room.spec.ts index 8763c0fd6..9e610766d 100644 --- a/playwright/e2e/knock/create-knock-room.spec.ts +++ b/playwright/e2e/knock/create-knock-room.spec.ts @@ -31,7 +31,7 @@ test.describe("Create Knock Room", () => { await dialog.getByRole("option", { name: "Ask to join" }).click(); await dialog.getByRole("button", { name: "Create room" }).click(); - await expect(page.locator(".mx_LegacyRoomHeader").getByText("Cybersecurity")).toBeVisible(); + await expect(page.locator(".mx_RoomHeader").getByText("Cybersecurity")).toBeVisible(); const urlHash = await page.evaluate(() => window.location.hash); const roomId = urlHash.replace("#/room/", ""); @@ -48,7 +48,7 @@ test.describe("Create Knock Room", () => { await dialog.getByRole("textbox", { name: "Name" }).fill("Cybersecurity"); await dialog.getByRole("button", { name: "Create room" }).click(); - await expect(page.locator(".mx_LegacyRoomHeader").getByText("Cybersecurity")).toBeVisible(); + await expect(page.locator(".mx_RoomHeader").getByText("Cybersecurity")).toBeVisible(); const urlHash = await page.evaluate(() => window.location.hash); const roomId = urlHash.replace("#/room/", ""); @@ -74,7 +74,7 @@ test.describe("Create Knock Room", () => { await dialog.getByText("Make this room visible in the public room directory.").click(); await dialog.getByRole("button", { name: "Create room" }).click(); - await expect(page.locator(".mx_LegacyRoomHeader").getByText("Cybersecurity")).toBeVisible(); + await expect(page.locator(".mx_RoomHeader").getByText("Cybersecurity")).toBeVisible(); const urlHash = await page.evaluate(() => window.location.hash); const roomId = urlHash.replace("#/room/", ""); diff --git a/playwright/e2e/lazy-loading/lazy-loading.spec.ts b/playwright/e2e/lazy-loading/lazy-loading.spec.ts index 8b8158981..1a20100d1 100644 --- a/playwright/e2e/lazy-loading/lazy-loading.spec.ts +++ b/playwright/e2e/lazy-loading/lazy-loading.spec.ts @@ -78,9 +78,10 @@ test.describe("Lazy Loading", () => { } } - async function openMemberlist(page: Page): Promise { - await page.locator(".mx_LegacyRoomHeader").getByRole("button", { name: "Room info" }).click(); - await page.locator(".mx_RoomSummaryCard").getByRole("menuitem", { name: "People" }).click(); // \d represents the number of the room members + async function openMemberlist(app: ElementAppPage): Promise { + await app.toggleRoomInfoPanel(); + const { page } = app; + await page.locator(".mx_RightPanelTabs").getByText("People").click(); } function getMemberInMemberlist(page: Page, name: string): Locator { @@ -123,7 +124,7 @@ test.describe("Lazy Loading", () => { // Alice should see 2 messages from every charly with the correct display name await checkPaginatedDisplayNames(app, charly1to5); - await openMemberlist(page); + await openMemberlist(app); await checkMemberList(page, charly1to5); await joinCharliesWhileAliceIsOffline(page, app, charly6to10); await checkMemberList(page, charly6to10); diff --git a/playwright/e2e/login/login.spec.ts b/playwright/e2e/login/login.spec.ts index 8d2e324e9..bbadf02d0 100644 --- a/playwright/e2e/login/login.spec.ts +++ b/playwright/e2e/login/login.spec.ts @@ -14,36 +14,37 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { test, expect } from "../../element-web-test"; +import { expect, test } from "../../element-web-test"; import { doTokenRegistration } from "./utils"; import { isDendrite } from "../../plugins/homeserver/dendrite"; +import { selectHomeserver } from "../utils"; test.describe("Login", () => { - test.describe("m.login.password", () => { + test.describe("Password login", () => { test.use({ startHomeserverOpts: "consent" }); const username = "user1234"; const password = ""; // :TCHAP: remove pwd to pass git guardian - test.beforeEach(async ({ page, homeserver }) => { + test.beforeEach(async ({ homeserver }) => { await homeserver.registerUser(username, password); - await page.goto("/#/login"); }); - test("logs in with an existing account and lands on the home screen", async ({ + test("Loads the welcome page by default; then logs in with an existing account and lands on the home screen", async ({ page, homeserver, checkA11y, }) => { - // first pick the homeserver, as otherwise the user picker won't be visible - await page.getByRole("button", { name: "Edit" }).click(); - await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.config.baseUrl); - await page.getByRole("button", { name: "Continue", exact: true }).click(); - // wait for the dialog to go away - await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0); + await page.goto("/"); - await expect(page.locator(".mx_Spinner")).toHaveCount(0); - await expect(page.locator(".mx_ServerPicker_server")).toHaveText(homeserver.config.baseUrl); + // Should give us the welcome page initially + await expect(page.getByRole("heading", { name: "Welcome to Element!" })).toBeVisible(); + + // Start the login process + await page.getByRole("link", { name: "Sign in" }).click(); + + // first pick the homeserver, as otherwise the user picker won't be visible + await selectHomeserver(page, homeserver.config.baseUrl); await page.getByRole("button", { name: "Edit" }).click(); @@ -56,14 +57,7 @@ test.describe("Login", () => { await expect(page.locator(".mx_ServerPicker_server")).toHaveText("server.invalid"); // switch back to the custom homeserver - await page.getByRole("button", { name: "Edit" }).click(); - await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.config.baseUrl); - await page.getByRole("button", { name: "Continue", exact: true }).click(); - // wait for the dialog to go away - await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0); - - await expect(page.locator(".mx_Spinner")).toHaveCount(0); - await expect(page.locator(".mx_ServerPicker_server")).toHaveText(homeserver.config.baseUrl); + await selectHomeserver(page, homeserver.config.baseUrl); await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible(); // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688 @@ -76,6 +70,20 @@ test.describe("Login", () => { await expect(page).toHaveURL(/\/#\/home$/); }); + + test("Follows the original link after login", async ({ page, homeserver }) => { + await page.goto("/#/room/!room:id"); // should redirect to the welcome page + await page.getByRole("link", { name: "Sign in" }).click(); + + await selectHomeserver(page, homeserver.config.baseUrl); + + await page.getByRole("textbox", { name: "Username" }).fill(username); + await page.getByPlaceholder("Password").fill(password); + await page.getByRole("button", { name: "Sign in" }).click(); + + await expect(page).toHaveURL(/\/#\/room\/!room:id$/); + await expect(page.getByRole("button", { name: "Join the discussion" })).toBeVisible(); + }); }); // tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server diff --git a/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts b/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts index 287ac77cd..3070d5fad 100644 --- a/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts +++ b/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts @@ -35,10 +35,10 @@ test.describe("1:1 chat room", () => { await page.goto(`/#/user/${user2.userId}?action=chat`); }); - test("should open new 1:1 chat room after leaving the old one", async ({ page, user2 }) => { + test("should open new 1:1 chat room after leaving the old one", async ({ page, app, user2 }) => { // leave 1:1 chat room - await page.locator(".mx_LegacyRoomHeader_nametext").getByText(user2.displayName).click(); - await page.getByRole("menuitem", { name: "Leave" }).click(); + await app.toggleRoomInfoPanel(); + await page.getByRole("menuitem", { name: "Leave room" }).click(); await page.getByRole("button", { name: "Leave" }).click(); // wait till the room was left @@ -49,6 +49,6 @@ test.describe("1:1 chat room", () => { // open new 1:1 chat room await page.goto(`/#/user/${user2.userId}?action=chat`); - await expect(page.locator(".mx_LegacyRoomHeader_nametext").getByText(user2.displayName)).toBeVisible(); + await expect(page.locator(".mx_RoomHeader_heading").getByText(user2.displayName)).toBeVisible(); }); }); diff --git a/playwright/e2e/polls/pollHistory.spec.ts b/playwright/e2e/polls/pollHistory.spec.ts index 458bb544c..e9ebf0a30 100644 --- a/playwright/e2e/polls/pollHistory.spec.ts +++ b/playwright/e2e/polls/pollHistory.spec.ts @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ import { test, expect } from "../../element-web-test"; -import type { Page } from "@playwright/test"; import type { Bot } from "../../pages/bot"; import type { Client } from "../../pages/client"; +import { ElementAppPage } from "../../pages/ElementAppPage"; test.describe("Poll history", () => { type CreatePollOptions = { @@ -66,8 +66,9 @@ test.describe("Poll history", () => { }); }; - async function openPollHistory(page: Page): Promise { - await page.getByRole("button", { name: "Room info" }).click(); + async function openPollHistory(app: ElementAppPage): Promise { + const { page } = app; + await app.toggleRoomInfoPanel(); await page.locator(".mx_RoomSummaryCard").getByRole("menuitem", { name: "Poll history" }).click(); } @@ -116,7 +117,7 @@ test.describe("Poll history", () => { await botVoteForOption(bot, roomId, pollId2, pollParams1.options[1].id); await endPoll(bot, roomId, pollId2); - await openPollHistory(page); + await openPollHistory(app); // these polls are also in the timeline // focus on the poll history dialog diff --git a/playwright/e2e/presence/presence.spec.ts b/playwright/e2e/presence/presence.spec.ts index 861181ba5..e52b97844 100644 --- a/playwright/e2e/presence/presence.spec.ts +++ b/playwright/e2e/presence/presence.spec.ts @@ -59,7 +59,7 @@ test.describe("Presence tests", () => { ); await app.client.createRoom({}); // trigger sync - await page.getByRole("button", { name: "Room info" }).click(); + await app.toggleRoomInfoPanel(); await page.locator(".mx_RightPanel").getByText("People").click(); await expect(page.locator(".mx_EntityTile_unreachable")).toContainText("Bob"); await expect(page.locator(".mx_EntityTile_unreachable")).toContainText("User's server unreachable"); diff --git a/playwright/e2e/read-receipts/editing-messages-in-threads.spec.ts b/playwright/e2e/read-receipts/editing-messages-in-threads.spec.ts new file mode 100644 index 000000000..62394cccb --- /dev/null +++ b/playwright/e2e/read-receipts/editing-messages-in-threads.spec.ts @@ -0,0 +1,191 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("editing messages", () => { + test.describe("in threads", () => { + test("An edit of a threaded message makes the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given we have read the thread + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.assertReadThread("Resp1"); + await util.goTo(room1); + + // When a message inside it is edited + await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); + + // Then the room and thread are read + await util.assertStillRead(room2); + await util.goTo(room2); + await util.assertReadThread("Msg1"); + }); + + test("Reading an edit of a threaded message makes the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an edited thread message appears after we read it + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.assertReadThread("Resp1"); + await util.goTo(room1); + await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); + await util.assertStillRead(room2); + + // When I read it + await util.goTo(room2); + await util.openThread("Msg1"); + + // Then the room and thread are still read + await util.assertStillRead(room2); + await util.assertReadThread("Msg1"); + }); + + test("Marking a room as read after an edit in a thread makes it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an edit in a thread is making the room unread + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Msg1", + msg.threadedOff("Msg1", "Resp1"), + msg.editOf("Resp1", "Edit1"), + ]); + await util.assertUnread(room2, 1); + + // When I mark the room as read + await util.markAsRead(room2); + + // Then it is read + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + }); + + test("Editing a thread message after marking as read leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a room is marked as read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.assertUnread(room2, 1); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When a message is edited + await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); + + // Then the room remains read + await util.assertStillRead(room2); + await util.assertReadThread("Msg1"); + }); + + test("A room with an edited threaded message is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an edit in a thread is leaving a room read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.markAsRead(room2); + await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); + await util.assertStillRead(room2); + + // When I restart + await util.saveAndReload(); + + // Then is it still read + await util.assertRead(room2); + }); + + test("A room where all threaded edits are read is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.assertUnread(room2, 1); + await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); + await util.assertUnread(room2, 1); + + await util.goTo(room2); + + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + await util.goTo(room1); // Make sure we are looking at room1 after reload + await util.assertStillRead(room2); + + await util.saveAndReload(); + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + }); + + test("A room where all threaded edits are marked as read is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Msg1", + msg.threadedOff("Msg1", "Resp1"), + msg.editOf("Resp1", "Edit1"), + ]); + await util.assertUnread(room2, 1); + await util.markAsRead(room2); + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + + // When I restart + await util.saveAndReload(); + + // It is still read + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/editing-messages-main-timeline.spec.ts b/playwright/e2e/read-receipts/editing-messages-main-timeline.spec.ts new file mode 100644 index 000000000..e03a011a4 --- /dev/null +++ b/playwright/e2e/read-receipts/editing-messages-main-timeline.spec.ts @@ -0,0 +1,180 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("editing messages", () => { + test.describe("in the main timeline", () => { + test("Editing a message leaves a room read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { + // Given I am not looking at the room + await util.goTo(room1); + + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + // When an edit appears in the room + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); + + // Then it remains read + await util.assertStillRead(room2); + }); + test("Reading an edit leaves the room read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { + // Given an edit is making the room unread + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); + await util.assertStillRead(room2); + + // When I read it + await util.goTo(room2); + + // Then the room stays read + await util.assertStillRead(room2); + await util.goTo(room1); + await util.assertStillRead(room2); + }); + test("Editing a message after marking as read leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given the room is marked as read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When a message is edited + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); + + // Then the room remains read + await util.assertStillRead(room2); + }); + test("Editing a reply after reading it makes the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given the room is all read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.replyTo("Msg1", "Reply1")]); + await util.assertUnread(room2, 2); + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + // When a message is edited + await util.receiveMessages(room2, [msg.editOf("Reply1", "Reply1 Edit1")]); + + // Then it remains read + await util.assertStillRead(room2); + }); + test("Editing a reply after marking as read makes the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a reply is marked as read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.replyTo("Msg1", "Reply1")]); + await util.assertUnread(room2, 2); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When the reply is edited + await util.receiveMessages(room2, [msg.editOf("Reply1", "Reply1 Edit1")]); + + // Then the room remains read + await util.assertStillRead(room2); + }); + test("A room with an edit is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a message is marked as read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When an edit appears in the room + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); + + // Then it remains read + await util.assertStillRead(room2); + + // And remains so after a reload + await util.saveAndReload(); + await util.assertStillRead(room2); + }); + test("An edited message becomes read if it happens while I am looking", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a message is marked as read + await util.goTo(room2); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertRead(room2); + + // When I see an edit appear in the room I am looking at + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); + + // Then it becomes read + await util.assertStillRead(room2); + }); + test("A room where all edits are read is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a message was edited and read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.editOf("Msg1", "Msg1 Edit1")]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.assertRead(room2); + + // When I reload + await util.saveAndReload(); + + // Then the room is still read + await util.assertRead(room2); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/editing-messages-thread-roots.spec.ts b/playwright/e2e/read-receipts/editing-messages-thread-roots.spec.ts new file mode 100644 index 000000000..279845f5d --- /dev/null +++ b/playwright/e2e/read-receipts/editing-messages-thread-roots.spec.ts @@ -0,0 +1,179 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("editing messages", () => { + test.describe("thread roots", () => { + test("An edit of a thread root leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I have read a thread + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Msg1"); + await util.backToThreadsList(); + await util.assertRead(room2); + await util.goTo(room1); + + // When the thread root is edited + await util.receiveMessages(room2, [msg.editOf("Msg1", "Edit1")]); + + // Then the room is read + await util.assertStillRead(room2); + + // And the thread is read + await util.goTo(room2); + await util.assertStillRead(room2); + await util.assertReadThread("Edit1"); + }); + + test("Reading an edit of a thread root leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a fully-read thread exists + await util.goTo(room2); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.goTo(room1); + await util.assertRead(room2); + + // When the thread root is edited + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); + + // And I read that edit + await util.goTo(room2); + + // Then the room becomes read and stays read + await util.assertStillRead(room2); + await util.goTo(room1); + await util.assertStillRead(room2); + }); + + test("Editing a thread root after reading leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a fully-read thread exists + await util.goTo(room2); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.goTo(room1); + + // When the thread root is edited + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); + + // Then the room stays read + await util.assertStillRead(room2); + }); + + test("Marking a room as read after an edit of a thread root keeps it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a fully-read thread exists + await util.goTo(room2); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.goTo(room1); + await util.assertRead(room2); + + // When the thread root is edited (and I receive another message + // to allow Mark as read) + await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1"), "Msg2"]); + + // And when I mark the room as read + await util.markAsRead(room2); + + // Then the room becomes read and stays read + await util.assertStillRead(room2); + await util.goTo(room1); + await util.assertStillRead(room2); + }); + + test("Editing a thread root that is a reply after marking as read leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread based on a reply exists and is read because it is marked as read + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Msg", + msg.replyTo("Msg", "Reply"), + msg.threadedOff("Reply", "InThread"), + ]); + await util.assertUnread(room2, 2); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When I edit the thread root + await util.receiveMessages(room2, [msg.editOf("Reply", "Edited Reply")]); + + // Then the room is read + await util.assertStillRead(room2); + + // And the thread is read + await util.goTo(room2); + await util.assertReadThread("Edited Reply"); + }); + + test("Marking a room as read after an edit of a thread root that is a reply leaves it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread based on a reply exists and the reply has been edited + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Msg", + msg.replyTo("Msg", "Reply"), + msg.threadedOff("Reply", "InThread"), + ]); + await util.receiveMessages(room2, [msg.editOf("Reply", "Edited Reply")]); + await util.assertUnread(room2, 2); + + // When I mark the room as read + await util.markAsRead(room2); + + // Then the room and thread are read + await util.assertStillRead(room2); + await util.goTo(room2); + await util.assertReadThread("Edited Reply"); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/editing-messages.spec.ts b/playwright/e2e/read-receipts/editing-messages.spec.ts deleted file mode 100644 index 5005ad62b..000000000 --- a/playwright/e2e/read-receipts/editing-messages.spec.ts +++ /dev/null @@ -1,504 +0,0 @@ -/* -Copyright 2023 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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. -*/ - -/* See readme.md for tips on writing these tests. */ - -import { test } from "."; - -test.describe("Read receipts", () => { - test.describe("editing messages", () => { - test.describe("in the main timeline", () => { - test("Editing a message leaves a room read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { - // Given I am not looking at the room - await util.goTo(room1); - - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - // When an edit appears in the room - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); - - // Then it remains read - await util.assertStillRead(room2); - }); - test("Reading an edit leaves the room read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { - // Given an edit is making the room unread - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); - await util.assertStillRead(room2); - - // When I read it - await util.goTo(room2); - - // Then the room stays read - await util.assertStillRead(room2); - await util.goTo(room1); - await util.assertStillRead(room2); - }); - test("Editing a message after marking as read leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given the room is marked as read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When a message is edited - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); - - // Then the room remains read - await util.assertStillRead(room2); - }); - test("Editing a reply after reading it makes the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given the room is all read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.replyTo("Msg1", "Reply1")]); - await util.assertUnread(room2, 2); - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - // When a message is edited - await util.receiveMessages(room2, [msg.editOf("Reply1", "Reply1 Edit1")]); - - // Then it remains read - await util.assertStillRead(room2); - }); - test("Editing a reply after marking as read makes the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a reply is marked as read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.replyTo("Msg1", "Reply1")]); - await util.assertUnread(room2, 2); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When the reply is edited - await util.receiveMessages(room2, [msg.editOf("Reply1", "Reply1 Edit1")]); - - // Then the room remains read - await util.assertStillRead(room2); - }); - test("A room with an edit is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a message is marked as read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When an edit appears in the room - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); - - // Then it remains read - await util.assertStillRead(room2); - - // And remains so after a reload - await util.saveAndReload(); - await util.assertStillRead(room2); - }); - test("An edited message becomes read if it happens while I am looking", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a message is marked as read - await util.goTo(room2); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertRead(room2); - - // When I see an edit appear in the room I am looking at - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); - - // Then it becomes read - await util.assertStillRead(room2); - }); - test("A room where all edits are read is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a message was edited and read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.editOf("Msg1", "Msg1 Edit1")]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.assertRead(room2); - - // When I reload - await util.saveAndReload(); - - // Then the room is still read - await util.assertRead(room2); - }); - }); - - test.describe("in threads", () => { - test("An edit of a threaded message makes the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given we have read the thread - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.assertReadThread("Resp1"); - await util.goTo(room1); - - // When a message inside it is edited - await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); - - // Then the room and thread are read - await util.assertStillRead(room2); - await util.goTo(room2); - await util.assertReadThread("Msg1"); - }); - - test("Reading an edit of a threaded message makes the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an edited thread message appears after we read it - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.assertReadThread("Resp1"); - await util.goTo(room1); - await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); - await util.assertStillRead(room2); - - // When I read it - await util.goTo(room2); - await util.openThread("Msg1"); - - // Then the room and thread are still read - await util.assertStillRead(room2); - await util.assertReadThread("Msg1"); - }); - - test("Marking a room as read after an edit in a thread makes it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an edit in a thread is making the room unread - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Msg1", - msg.threadedOff("Msg1", "Resp1"), - msg.editOf("Resp1", "Edit1"), - ]); - await util.assertUnread(room2, 1); - - // When I mark the room as read - await util.markAsRead(room2); - - // Then it is read - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - }); - - test("Editing a thread message after marking as read leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a room is marked as read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.assertUnread(room2, 1); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When a message is edited - await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); - - // Then the room remains read - await util.assertStillRead(room2); - await util.assertReadThread("Msg1"); - }); - - test("A room with an edited threaded message is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an edit in a thread is leaving a room read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.markAsRead(room2); - await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); - await util.assertStillRead(room2); - - // When I restart - await util.saveAndReload(); - - // Then is it still read - await util.assertRead(room2); - }); - - test("A room where all threaded edits are read is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.assertUnread(room2, 1); - await util.receiveMessages(room2, [msg.editOf("Resp1", "Edit1")]); - await util.assertUnread(room2, 1); - - await util.goTo(room2); - - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - await util.goTo(room1); // Make sure we are looking at room1 after reload - await util.assertStillRead(room2); - - await util.saveAndReload(); - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - }); - - test("A room where all threaded edits are marked as read is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Msg1", - msg.threadedOff("Msg1", "Resp1"), - msg.editOf("Resp1", "Edit1"), - ]); - await util.assertUnread(room2, 1); - await util.markAsRead(room2); - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - - // When I restart - await util.saveAndReload(); - - // It is still read - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - }); - }); - - test.describe("thread roots", () => { - test("An edit of a thread root leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I have read a thread - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Msg1"); - await util.backToThreadsList(); - await util.assertRead(room2); - await util.goTo(room1); - - // When the thread root is edited - await util.receiveMessages(room2, [msg.editOf("Msg1", "Edit1")]); - - // Then the room is read - await util.assertStillRead(room2); - - // And the thread is read - await util.goTo(room2); - await util.assertStillRead(room2); - await util.assertReadThread("Edit1"); - }); - - test("Reading an edit of a thread root leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a fully-read thread exists - await util.goTo(room2); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.goTo(room1); - await util.assertRead(room2); - - // When the thread root is edited - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); - - // And I read that edit - await util.goTo(room2); - - // Then the room becomes read and stays read - await util.assertStillRead(room2); - await util.goTo(room1); - await util.assertStillRead(room2); - }); - - test("Editing a thread root after reading leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a fully-read thread exists - await util.goTo(room2); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.goTo(room1); - - // When the thread root is edited - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1")]); - - // Then the room stays read - await util.assertStillRead(room2); - }); - - test("Marking a room as read after an edit of a thread root keeps it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a fully-read thread exists - await util.goTo(room2); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.goTo(room1); - await util.assertRead(room2); - - // When the thread root is edited (and I receive another message - // to allow Mark as read) - await util.receiveMessages(room2, [msg.editOf("Msg1", "Msg1 Edit1"), "Msg2"]); - - // And when I mark the room as read - await util.markAsRead(room2); - - // Then the room becomes read and stays read - await util.assertStillRead(room2); - await util.goTo(room1); - await util.assertStillRead(room2); - }); - - test("Editing a thread root that is a reply after marking as read leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread based on a reply exists and is read because it is marked as read - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Msg", - msg.replyTo("Msg", "Reply"), - msg.threadedOff("Reply", "InThread"), - ]); - await util.assertUnread(room2, 2); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When I edit the thread root - await util.receiveMessages(room2, [msg.editOf("Reply", "Edited Reply")]); - - // Then the room is read - await util.assertStillRead(room2); - - // And the thread is read - await util.goTo(room2); - await util.assertReadThread("Edited Reply"); - }); - - test("Marking a room as read after an edit of a thread root that is a reply leaves it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread based on a reply exists and the reply has been edited - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Msg", - msg.replyTo("Msg", "Reply"), - msg.threadedOff("Reply", "InThread"), - ]); - await util.receiveMessages(room2, [msg.editOf("Reply", "Edited Reply")]); - await util.assertUnread(room2, 2); - - // When I mark the room as read - await util.markAsRead(room2); - - // Then the room and thread are read - await util.assertStillRead(room2); - await util.goTo(room2); - await util.assertReadThread("Edited Reply"); - }); - }); - }); -}); diff --git a/playwright/e2e/read-receipts/high-level.spec.ts b/playwright/e2e/read-receipts/high-level.spec.ts index e237afd64..a3c2c0de3 100644 --- a/playwright/e2e/read-receipts/high-level.spec.ts +++ b/playwright/e2e/read-receipts/high-level.spec.ts @@ -19,77 +19,6 @@ limitations under the License. import { customEvent, many, test } from "."; test.describe("Read receipts", () => { - test.describe("Message ordering", () => { - test.describe("in the main timeline", () => { - test.fixme( - "A receipt for the last event in sync order (even with wrong ts) marks a room as read", - () => {}, - ); - test.fixme( - "A receipt for a non-last event in sync order (even when ts makes it last) leaves room unread", - () => {}, - ); - }); - - test.describe("in threads", () => { - // These don't pass yet - we need MSC4033 - we don't even know the Sync order yet - test.fixme( - "A receipt for the last event in sync order (even with wrong ts) marks a thread as read", - () => {}, - ); - test.fixme( - "A receipt for a non-last event in sync order (even when ts makes it last) leaves thread unread", - () => {}, - ); - - // These pass now and should not later - we should use order from MSC4033 instead of ts - // These are broken out - test.fixme( - "A receipt for last threaded event in ts order (even when it was received non-last) marks a thread as read", - () => {}, - ); - test.fixme( - "A receipt for non-last threaded event in ts order (even when it was received last) leaves thread unread", - () => {}, - ); - test.fixme( - "A receipt for last threaded edit in ts order (even when it was received non-last) marks a thread as read", - () => {}, - ); - test.fixme( - "A receipt for non-last threaded edit in ts order (even when it was received last) leaves thread unread", - () => {}, - ); - test.fixme( - "A receipt for last threaded reaction in ts order (even when it was received non-last) marks a thread as read", - () => {}, - ); - test.fixme( - "A receipt for non-last threaded reaction in ts order (even when it was received last) leaves thread unread", - () => {}, - ); - }); - - test.describe("thread roots", () => { - test.fixme( - "A receipt for last reaction to thread root in sync order (even when ts makes it last) marks room as read", - () => {}, - ); - test.fixme( - "A receipt for non-last reaction to thread root in sync order (even when ts makes it last) leaves room unread", - () => {}, - ); - test.fixme( - "A receipt for last edit to thread root in sync order (even when ts makes it last) marks room as read", - () => {}, - ); - test.fixme( - "A receipt for non-last edit to thread root in sync order (even when ts makes it last) leaves room unread", - () => {}, - ); - }); - }); - test.describe("Ignored events", () => { test("If all events after receipt are unimportant, the room is read", async ({ roomAlpha: room1, @@ -249,7 +178,6 @@ test.describe("Read receipts", () => { }); test("Paging up to find old threads that were never read keeps the room unread", async ({ - cryptoBackend, roomAlpha: room1, roomBeta: room2, util, @@ -338,7 +266,6 @@ test.describe("Read receipts", () => { }); test("After marking room as read, paging up to find old threads that were never read leaves the room read", async ({ - cryptoBackend, roomAlpha: room1, roomBeta: room2, util, @@ -416,79 +343,4 @@ test.describe("Read receipts", () => { await util.assertReadThread("Root3"); }); }); - - test.describe("Room list order", () => { - test("Rooms with unread messages appear at the top of room list if 'unread first' is selected", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - page, - }) => { - await util.goTo(room2); - - // Display the unread first room - await util.toggleRoomUnreadOrder(); - await util.receiveMessages(room1, ["Msg1"]); - await page.reload(); - - // Room 1 has an unread message and should be displayed first - await util.assertRoomListOrder([room1, room2]); - }); - - test("Rooms with unread threads appear at the top of room list if 'unread first' is selected", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - await util.goTo(room2); - await util.receiveMessages(room1, ["Msg1"]); - await util.markAsRead(room1); - await util.assertRead(room1); - - // Display the unread first room - await util.toggleRoomUnreadOrder(); - await util.receiveMessages(room1, [msg.threadedOff("Msg1", "Resp1")]); - await util.saveAndReload(); - - // Room 1 has an unread message and should be displayed first - await util.assertRoomListOrder([room1, room2]); - }); - }); - - test.describe("Notifications", () => { - test.describe("in the main timeline", () => { - test.fixme("A new message that mentions me shows a notification", () => {}); - test.fixme( - "Reading a notifying message reduces the notification count in the room list, space and tab", - () => {}, - ); - test.fixme( - "Reading the last notifying message removes the notification marker from room list, space and tab", - () => {}, - ); - test.fixme("Editing a message to mentions me shows a notification", () => {}); - test.fixme("Reading the last notifying edited message removes the notification marker", () => {}); - test.fixme("Redacting a notifying message removes the notification marker", () => {}); - }); - - test.describe("in threads", () => { - test.fixme("A new threaded message that mentions me shows a notification", () => {}); - test.fixme("Reading a notifying threaded message removes the notification count", () => {}); - test.fixme( - "Notification count remains steady when reading threads that contain seen notifications", - () => {}, - ); - test.fixme( - "Notification count remains steady when paging up thread view even when threads contain seen notifications", - () => {}, - ); - test.fixme( - "Notification count remains steady when paging up thread view after mark as unread even if older threads contain notifications", - () => {}, - ); - test.fixme("Redacting a notifying threaded message removes the notification marker", () => {}); - }); - }); }); diff --git a/playwright/e2e/read-receipts/index.ts b/playwright/e2e/read-receipts/index.ts index 4dd0450fb..1b6719290 100644 --- a/playwright/e2e/read-receipts/index.ts +++ b/playwright/e2e/read-receipts/index.ts @@ -399,11 +399,10 @@ class Helpers { } /** - * Close the threads panel. (Actually, close any right panel, but for these - * tests we only open the threads panel.) + * Close the threads panel. */ async closeThreadsPanel() { - await this.page.locator(".mx_RightPanel").getByLabel("Close").click(); + await this.page.locator(".mx_RoomHeader").getByLabel("Threads").click(); await expect(this.page.locator(".mx_RightPanel")).not.toBeVisible(); } @@ -411,7 +410,7 @@ class Helpers { * Return to the list of threads, given we are viewing a single thread. */ async backToThreadsList() { - await this.page.locator(".mx_RightPanel").getByLabel("Threads").click(); + await this.page.locator(".mx_RoomHeader").getByLabel("Threads").click(); } /** @@ -531,15 +530,14 @@ class Helpers { // whether it's open or not - wait here to give it a chance to settle. await this.page.waitForTimeout(200); - const ariaCurrent = await this.page.getByTestId("threadsButton").getAttribute("aria-current"); - if (ariaCurrent !== "true") { - await this.page.getByTestId("threadsButton").click(); - } - const threadPanel = this.page.locator(".mx_ThreadPanel"); + const isThreadPanelOpen = (await threadPanel.count()) !== 0; + if (!isThreadPanelOpen) { + await this.page.locator(".mx_RoomHeader").getByLabel("Threads").click(); + } await expect(threadPanel).toBeVisible(); await threadPanel.evaluate(($panel) => { - const $button = $panel.querySelector('.mx_BaseCard_back[aria-label="Threads"]'); + const $button = $panel.querySelector('[data-testid="base-card-back-button"]'); // If the Threads back button is present then click it - the // threads button can open either threads list or thread panel if ($button) { diff --git a/playwright/e2e/read-receipts/message-ordering.spec.ts b/playwright/e2e/read-receipts/message-ordering.spec.ts new file mode 100644 index 000000000..73c640d35 --- /dev/null +++ b/playwright/e2e/read-receipts/message-ordering.spec.ts @@ -0,0 +1,92 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("Message ordering", () => { + test.describe("in the main timeline", () => { + test.fixme( + "A receipt for the last event in sync order (even with wrong ts) marks a room as read", + () => {}, + ); + test.fixme( + "A receipt for a non-last event in sync order (even when ts makes it last) leaves room unread", + () => {}, + ); + }); + + test.describe("in threads", () => { + // These don't pass yet - we need MSC4033 - we don't even know the Sync order yet + test.fixme( + "A receipt for the last event in sync order (even with wrong ts) marks a thread as read", + () => {}, + ); + test.fixme( + "A receipt for a non-last event in sync order (even when ts makes it last) leaves thread unread", + () => {}, + ); + + // These pass now and should not later - we should use order from MSC4033 instead of ts + // These are broken out + test.fixme( + "A receipt for last threaded event in ts order (even when it was received non-last) marks a thread as read", + () => {}, + ); + test.fixme( + "A receipt for non-last threaded event in ts order (even when it was received last) leaves thread unread", + () => {}, + ); + test.fixme( + "A receipt for last threaded edit in ts order (even when it was received non-last) marks a thread as read", + () => {}, + ); + test.fixme( + "A receipt for non-last threaded edit in ts order (even when it was received last) leaves thread unread", + () => {}, + ); + test.fixme( + "A receipt for last threaded reaction in ts order (even when it was received non-last) marks a thread as read", + () => {}, + ); + test.fixme( + "A receipt for non-last threaded reaction in ts order (even when it was received last) leaves thread unread", + () => {}, + ); + }); + + test.describe("thread roots", () => { + test.fixme( + "A receipt for last reaction to thread root in sync order (even when ts makes it last) marks room as read", + () => {}, + ); + test.fixme( + "A receipt for non-last reaction to thread root in sync order (even when ts makes it last) leaves room unread", + () => {}, + ); + test.fixme( + "A receipt for last edit to thread root in sync order (even when ts makes it last) marks room as read", + () => {}, + ); + test.fixme( + "A receipt for non-last edit to thread root in sync order (even when ts makes it last) leaves room unread", + () => {}, + ); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/new-messages.spec.ts b/playwright/e2e/read-receipts/new-messages-in-threads.spec.ts similarity index 55% rename from playwright/e2e/read-receipts/new-messages.spec.ts rename to playwright/e2e/read-receipts/new-messages-in-threads.spec.ts index 97308a4bb..37b43bae1 100644 --- a/playwright/e2e/read-receipts/new-messages.spec.ts +++ b/playwright/e2e/read-receipts/new-messages-in-threads.spec.ts @@ -20,151 +20,6 @@ import { many, test } from "."; test.describe("Read receipts", () => { test.describe("new messages", () => { - test.describe("in the main timeline", () => { - test("Receiving a message makes a room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I am in a different room - await util.goTo(room1); - await util.assertRead(room2); - - // When I receive some messages - await util.receiveMessages(room2, ["Msg1"]); - - // Then the room is marked as unread - await util.assertUnread(room2, 1); - }); - test("Reading latest message makes the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I have some unread messages - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - - // When I read the main timeline - await util.goTo(room2); - - // Then the room becomes read - await util.assertRead(room2); - }); - test("Reading an older message leaves the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given there are lots of messages in a room - await util.goTo(room1); - await util.receiveMessages(room2, many("Msg", 30)); - await util.assertUnread(room2, 30); - - // When I jump to one of the older messages - await msg.jumpTo(room2.name, "Msg0001"); - - // Then the room is still unread, but some messages were read - await util.assertUnreadLessThan(room2, 30); - }); - test("Marking a room as read makes it read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { - // Given I have some unread messages - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - - // When I mark the room as read - await util.markAsRead(room2); - - // Then it is read - await util.assertRead(room2); - }); - test("Receiving a new message after marking as read makes it unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I have marked my messages as read - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When I receive a new message - await util.receiveMessages(room2, ["Msg2"]); - - // Then the room is unread - await util.assertUnread(room2, 1); - }); - test("A room with a new message is still unread after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I have an unread message - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - - // When I restart - await util.saveAndReload(); - - // Then I still have an unread message - await util.assertUnread(room2, 1); - }); - test("A room where all messages are read is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I have read all messages - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.assertRead(room2); - - // When I restart - await util.saveAndReload(); - - // Then all messages are still read - await util.assertRead(room2); - }); - test("A room that was marked as read is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I have marked all messages as read - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1"]); - await util.assertUnread(room2, 1); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When I restart - await util.saveAndReload(); - - // Then all messages are still read - await util.assertRead(room2); - }); - }); - test.describe("in threads", () => { test("Receiving a message makes a room unread", async ({ roomAlpha: room1, @@ -450,100 +305,5 @@ test.describe("Read receipts", () => { await util.assertReadThread("Msg1"); }); }); - - test.describe("thread roots", () => { - test("Reading a thread root does not mark the thread as read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread exists - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); - await util.assertUnread(room2, 1); // (Sanity) - - // When I read the main timeline - await util.goTo(room2); - - // Then room doesn't appear unread but the thread does - await util.assertRead(room2); - await util.assertUnreadThread("Msg1"); - }); - - test("Reading a thread root within the thread view marks it as read in the main timeline", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given lots of messages are on the main timeline, and one has a thread off it - await util.goTo(room1); - await util.receiveMessages(room2, [ - ...many("beforeThread", 30), - "ThreadRoot", - msg.threadedOff("ThreadRoot", "InThread"), - ...many("afterThread", 30), - ]); - await util.assertUnread(room2, 61); // Sanity - - // When I jump to an old message and read the thread - await msg.jumpTo(room2.name, "beforeThread0000"); - // When the thread is opened, the timeline is scrolled until the thread root reached the center - await util.openThread("ThreadRoot"); - - // Then the thread root is marked as read in the main timeline, - // 30 remaining messages are unread - 7 messages are displayed under the thread root - await util.assertUnread(room2, 30 - 7); - }); - - test("Creating a new thread based on a reply makes the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a message and reply exist and are read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", msg.replyTo("Msg1", "Reply1")]); - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - await util.assertRead(room2); - - // When I receive a thread message created on the reply - await util.receiveMessages(room2, [msg.threadedOff("Reply1", "Resp1")]); - - // Then the thread is unread - await util.goTo(room2); - await util.assertUnreadThread("Reply1"); - }); - - test("Reading a thread whose root is a reply makes the thread read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an unread thread off a reply exists - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Msg1", - msg.replyTo("Msg1", "Reply1"), - msg.threadedOff("Reply1", "Resp1"), - ]); - await util.assertUnread(room2, 2); - await util.goTo(room2); - await util.assertRead(room2); - await util.assertUnreadThread("Reply1"); - - // When I read the thread - await util.openThread("Reply1"); - - // Then the room and thread are read - await util.assertRead(room2); - await util.assertReadThread("Reply1"); - }); - }); }); }); diff --git a/playwright/e2e/read-receipts/new-messages-main-timeline.spec.ts b/playwright/e2e/read-receipts/new-messages-main-timeline.spec.ts new file mode 100644 index 000000000..eb528f281 --- /dev/null +++ b/playwright/e2e/read-receipts/new-messages-main-timeline.spec.ts @@ -0,0 +1,168 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { many, test } from "."; + +test.describe("Read receipts", () => { + test.describe("new messages", () => { + test.describe("in the main timeline", () => { + test("Receiving a message makes a room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I am in a different room + await util.goTo(room1); + await util.assertRead(room2); + + // When I receive some messages + await util.receiveMessages(room2, ["Msg1"]); + + // Then the room is marked as unread + await util.assertUnread(room2, 1); + }); + test("Reading latest message makes the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I have some unread messages + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + + // When I read the main timeline + await util.goTo(room2); + + // Then the room becomes read + await util.assertRead(room2); + }); + test("Reading an older message leaves the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given there are lots of messages in a room + await util.goTo(room1); + await util.receiveMessages(room2, many("Msg", 30)); + await util.assertUnread(room2, 30); + + // When I jump to one of the older messages + await msg.jumpTo(room2.name, "Msg0001"); + + // Then the room is still unread, but some messages were read + await util.assertUnreadLessThan(room2, 30); + }); + test("Marking a room as read makes it read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { + // Given I have some unread messages + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + + // When I mark the room as read + await util.markAsRead(room2); + + // Then it is read + await util.assertRead(room2); + }); + test("Receiving a new message after marking as read makes it unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I have marked my messages as read + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When I receive a new message + await util.receiveMessages(room2, ["Msg2"]); + + // Then the room is unread + await util.assertUnread(room2, 1); + }); + test("A room with a new message is still unread after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I have an unread message + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + + // When I restart + await util.saveAndReload(); + + // Then I still have an unread message + await util.assertUnread(room2, 1); + }); + test("A room where all messages are read is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I have read all messages + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.assertRead(room2); + + // When I restart + await util.saveAndReload(); + + // Then all messages are still read + await util.assertRead(room2); + }); + test("A room that was marked as read is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I have marked all messages as read + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1"]); + await util.assertUnread(room2, 1); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When I restart + await util.saveAndReload(); + + // Then all messages are still read + await util.assertRead(room2); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts b/playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts new file mode 100644 index 000000000..526bac4bf --- /dev/null +++ b/playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts @@ -0,0 +1,118 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { many, test } from "."; + +test.describe("Read receipts", () => { + test.describe("new messages", () => { + test.describe("thread roots", () => { + test("Reading a thread root does not mark the thread as read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread exists + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); + await util.assertUnread(room2, 1); // (Sanity) + + // When I read the main timeline + await util.goTo(room2); + + // Then room doesn't appear unread but the thread does + await util.assertRead(room2); + await util.assertUnreadThread("Msg1"); + }); + + test("Reading a thread root within the thread view marks it as read in the main timeline", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given lots of messages are on the main timeline, and one has a thread off it + await util.goTo(room1); + await util.receiveMessages(room2, [ + ...many("beforeThread", 30), + "ThreadRoot", + msg.threadedOff("ThreadRoot", "InThread"), + ...many("afterThread", 30), + ]); + await util.assertUnread(room2, 61); // Sanity + + // When I jump to an old message and read the thread + await msg.jumpTo(room2.name, "beforeThread0000"); + // When the thread is opened, the timeline is scrolled until the thread root reached the center + await util.openThread("ThreadRoot"); + + // Then the thread root is marked as read in the main timeline, + // 30 remaining messages are unread - 7 messages are displayed under the thread root + await util.assertUnread(room2, 30 - 7); + }); + + test("Creating a new thread based on a reply makes the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a message and reply exist and are read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", msg.replyTo("Msg1", "Reply1")]); + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + await util.assertRead(room2); + + // When I receive a thread message created on the reply + await util.receiveMessages(room2, [msg.threadedOff("Reply1", "Resp1")]); + + // Then the thread is unread + await util.goTo(room2); + await util.assertUnreadThread("Reply1"); + }); + + test("Reading a thread whose root is a reply makes the thread read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an unread thread off a reply exists + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Msg1", + msg.replyTo("Msg1", "Reply1"), + msg.threadedOff("Reply1", "Resp1"), + ]); + await util.assertUnread(room2, 2); + await util.goTo(room2); + await util.assertRead(room2); + await util.assertUnreadThread("Reply1"); + + // When I read the thread + await util.openThread("Reply1"); + + // Then the room and thread are read + await util.assertRead(room2); + await util.assertReadThread("Reply1"); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/notifications.spec.ts b/playwright/e2e/read-receipts/notifications.spec.ts new file mode 100644 index 000000000..5d87de1bb --- /dev/null +++ b/playwright/e2e/read-receipts/notifications.spec.ts @@ -0,0 +1,56 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("Notifications", () => { + test.describe("in the main timeline", () => { + test.fixme("A new message that mentions me shows a notification", () => {}); + test.fixme( + "Reading a notifying message reduces the notification count in the room list, space and tab", + () => {}, + ); + test.fixme( + "Reading the last notifying message removes the notification marker from room list, space and tab", + () => {}, + ); + test.fixme("Editing a message to mentions me shows a notification", () => {}); + test.fixme("Reading the last notifying edited message removes the notification marker", () => {}); + test.fixme("Redacting a notifying message removes the notification marker", () => {}); + }); + + test.describe("in threads", () => { + test.fixme("A new threaded message that mentions me shows a notification", () => {}); + test.fixme("Reading a notifying threaded message removes the notification count", () => {}); + test.fixme( + "Notification count remains steady when reading threads that contain seen notifications", + () => {}, + ); + test.fixme( + "Notification count remains steady when paging up thread view even when threads contain seen notifications", + () => {}, + ); + test.fixme( + "Notification count remains steady when paging up thread view after mark as unread even if older threads contain notifications", + () => {}, + ); + test.fixme("Redacting a notifying threaded message removes the notification marker", () => {}); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/reactions.spec.ts b/playwright/e2e/read-receipts/reactions-in-threads.spec.ts similarity index 56% rename from playwright/e2e/read-receipts/reactions.spec.ts rename to playwright/e2e/read-receipts/reactions-in-threads.spec.ts index 69208e5fc..dcd97ac43 100644 --- a/playwright/e2e/read-receipts/reactions.spec.ts +++ b/playwright/e2e/read-receipts/reactions-in-threads.spec.ts @@ -20,82 +20,6 @@ import { test, expect } from "."; test.describe("Read receipts", () => { test.describe("reactions", () => { - test.describe("in the main timeline", () => { - test("Receiving a reaction to a message does not make a room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - - // When I read the main timeline - await util.goTo(room2); - await util.assertRead(room2); - - await util.goTo(room1); - await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); - await util.assertRead(room2); - }); - test("Reacting to a message after marking as read does not make the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - - await util.markAsRead(room2); - await util.assertRead(room2); - - await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); - await util.assertRead(room2); - }); - test("A room with an unread reaction is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - - await util.markAsRead(room2); - await util.assertRead(room2); - - await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); - await util.assertRead(room2); - - await util.saveAndReload(); - await util.assertRead(room2); - }); - test("A room where all reactions are read is still read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1", "Msg2", msg.reactionTo("Msg2", "🪿")]); - await util.assertUnread(room2, 2); - - await util.markAsRead(room2); - await util.assertRead(room2); - - await util.saveAndReload(); - await util.assertRead(room2); - }); - }); - test.describe("in threads", () => { test("A reaction to a threaded message does not make the room unread", async ({ roomAlpha: room1, @@ -281,97 +205,5 @@ test.describe("Read receipts", () => { await expect(await page.locator(".mx_ThreadPanel").getByLabel("Mae reacted with 😀")).not.toBeVisible(); }); }); - - test.describe("thread roots", () => { - test("A reaction to a thread root does not make the room unread", async ({ - page, - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a read thread root exists - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Reply1")]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - - // When someone reacts to it - await util.goTo(room1); - await util.receiveMessages(room2, [msg.reactionTo("Msg1", "🪿")]); - await page.waitForTimeout(200); - - // Then the room is still read - await util.assertRead(room2); - // as is the thread - await util.assertReadThread("Msg1"); - }); - - test("Reading a reaction to a thread root leaves the room read", async ({ - page, - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a read thread root exists - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Reply1")]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Msg1"); - await util.assertRead(room2); - - // And the reaction to it does not make us unread - await util.goTo(room1); - await util.receiveMessages(room2, [msg.reactionTo("Msg1", "🪿")]); - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - - // When we read the reaction and go away again - await util.goTo(room2); - await util.openThread("Msg1"); - await util.assertRead(room2); - await util.goTo(room1); - await page.waitForTimeout(200); - - // Then the room is still read - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - }); - - test("Reacting to a thread root after marking as read makes the room unread but not the thread", async ({ - page, - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread root exists - await util.goTo(room1); - await util.assertRead(room2); - await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Reply1")]); - await util.assertUnread(room2, 1); - - // And we have marked the room as read - await util.markAsRead(room2); - await util.assertRead(room2); - await util.assertReadThread("Msg1"); - - // When someone reacts to it - await util.receiveMessages(room2, [msg.reactionTo("Msg1", "🪿")]); - await page.waitForTimeout(200); - - // Then the room is still read - await util.assertRead(room2); - // as is the thread - await util.assertReadThread("Msg1"); - }); - }); }); }); diff --git a/playwright/e2e/read-receipts/reactions-main-timeline.spec.ts b/playwright/e2e/read-receipts/reactions-main-timeline.spec.ts new file mode 100644 index 000000000..54f0c89af --- /dev/null +++ b/playwright/e2e/read-receipts/reactions-main-timeline.spec.ts @@ -0,0 +1,99 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("reactions", () => { + test.describe("in the main timeline", () => { + test("Receiving a reaction to a message does not make a room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + + // When I read the main timeline + await util.goTo(room2); + await util.assertRead(room2); + + await util.goTo(room1); + await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); + await util.assertRead(room2); + }); + test("Reacting to a message after marking as read does not make the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + + await util.markAsRead(room2); + await util.assertRead(room2); + + await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); + await util.assertRead(room2); + }); + test("A room with an unread reaction is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + + await util.markAsRead(room2); + await util.assertRead(room2); + + await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); + await util.assertRead(room2); + + await util.saveAndReload(); + await util.assertRead(room2); + }); + test("A room where all reactions are read is still read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1", "Msg2", msg.reactionTo("Msg2", "🪿")]); + await util.assertUnread(room2, 2); + + await util.markAsRead(room2); + await util.assertRead(room2); + + await util.saveAndReload(); + await util.assertRead(room2); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/reactions-thread-roots.spec.ts b/playwright/e2e/read-receipts/reactions-thread-roots.spec.ts new file mode 100644 index 000000000..9c1be63e5 --- /dev/null +++ b/playwright/e2e/read-receipts/reactions-thread-roots.spec.ts @@ -0,0 +1,115 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("reactions", () => { + test.describe("thread roots", () => { + test("A reaction to a thread root does not make the room unread", async ({ + page, + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a read thread root exists + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Reply1")]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + + // When someone reacts to it + await util.goTo(room1); + await util.receiveMessages(room2, [msg.reactionTo("Msg1", "🪿")]); + await page.waitForTimeout(200); + + // Then the room is still read + await util.assertRead(room2); + // as is the thread + await util.assertReadThread("Msg1"); + }); + + test("Reading a reaction to a thread root leaves the room read", async ({ + page, + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a read thread root exists + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Reply1")]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Msg1"); + await util.assertRead(room2); + + // And the reaction to it does not make us unread + await util.goTo(room1); + await util.receiveMessages(room2, [msg.reactionTo("Msg1", "🪿")]); + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + + // When we read the reaction and go away again + await util.goTo(room2); + await util.openThread("Msg1"); + await util.assertRead(room2); + await util.goTo(room1); + await page.waitForTimeout(200); + + // Then the room is still read + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + }); + + test("Reacting to a thread root after marking as read makes the room unread but not the thread", async ({ + page, + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread root exists + await util.goTo(room1); + await util.assertRead(room2); + await util.receiveMessages(room2, ["Msg1", msg.threadedOff("Msg1", "Reply1")]); + await util.assertUnread(room2, 1); + + // And we have marked the room as read + await util.markAsRead(room2); + await util.assertRead(room2); + await util.assertReadThread("Msg1"); + + // When someone reacts to it + await util.receiveMessages(room2, [msg.reactionTo("Msg1", "🪿")]); + await page.waitForTimeout(200); + + // Then the room is still read + await util.assertRead(room2); + // as is the thread + await util.assertReadThread("Msg1"); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/redactions.spec.ts b/playwright/e2e/read-receipts/redactions-in-threads.spec.ts similarity index 52% rename from playwright/e2e/read-receipts/redactions.spec.ts rename to playwright/e2e/read-receipts/redactions-in-threads.spec.ts index f7affbed2..323748e7e 100644 --- a/playwright/e2e/read-receipts/redactions.spec.ts +++ b/playwright/e2e/read-receipts/redactions-in-threads.spec.ts @@ -20,314 +20,6 @@ import { test } from "."; test.describe("Read receipts", () => { test.describe("redactions", () => { - test.describe("in the main timeline", () => { - test("Redacting the message pointed to by my receipt leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given I have read the messages in a room - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - // When the latest message is redacted - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - - // Then the room remains read - await util.assertStillRead(room2); - }); - - test("Reading an unread room after a redaction of the latest message makes it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an unread room - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - - // And the latest message has been redacted - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - - // When I read the room - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - // Then it becomes read - await util.assertStillRead(room2); - }); - test("Reading an unread room after a redaction of an older message makes it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an unread room with an earlier redaction - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); - - // When I read the room - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - // Then it becomes read - await util.assertStillRead(room2); - }); - test("Marking an unread room as read after a redaction makes it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an unread room where latest message is redacted - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.assertUnread(room2, 1); - - // When I mark it as read - await util.markAsRead(room2); - - // Then it becomes read - await util.assertRead(room2); - }); - test("Sending and redacting a message after marking the room as read makes it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a room that is marked as read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When a message is sent and then redacted - await util.receiveMessages(room2, ["Msg3"]); - await util.assertUnread(room2, 1); - await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); - - // Then the room is read - await util.assertRead(room2); - }); - test("Redacting a message after marking the room as read leaves it read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a room that is marked as read - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2", "Msg3"]); - await util.assertUnread(room2, 3); - await util.markAsRead(room2); - await util.assertRead(room2); - - // When we redact some messages - await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); - await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); - - // Then it is still read - await util.assertStillRead(room2); - }); - test("Redacting one of the unread messages reduces the unread count", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an unread room - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2", "Msg3"]); - await util.assertUnread(room2, 3); - - // When I redact a non-latest message - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - - // Then the unread count goes down - await util.assertUnread(room2, 2); - - // And when I redact the latest message - await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); - - // Then the unread count goes down again - await util.assertUnread(room2, 1); - }); - test("Redacting one of the unread messages reduces the unread count after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given unread count was reduced by redacting messages - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2", "Msg3"]); - await util.assertUnread(room2, 3); - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); - await util.assertUnread(room2, 1); - - // When I restart - await util.saveAndReload(); - - // Then the unread count is still reduced - await util.assertUnread(room2, 1); - }); - test("Redacting all unread messages makes the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an unread room - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - - // When I redact all the unread messages - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); - - // Then the room is back to being read - await util.assertRead(room2); - }); - test("Redacting all unread messages makes the room read after restart", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given all unread messages were redacted - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); - await util.assertRead(room2); - - // When I restart - await util.saveAndReload(); - - // Then the room is still read - await util.assertRead(room2); - }); - test("Reacting to a redacted message leaves the room read", async ({ - page, - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a redacted message exists - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.assertUnread(room2, 1); - - // And the room is read - await util.goTo(room2); - await util.assertRead(room2); - await page.waitForTimeout(200); - await util.goTo(room1); - - // When I react to the redacted message - await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); - - // Then the room is still read - await util.assertStillRead(room2); - }); - test("Editing a redacted message leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a redacted message exists - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.assertUnread(room2, 1); - - // And the room is read - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - // When I attempt to edit the redacted message - await util.receiveMessages(room2, [msg.editOf("Msg2", "Msg2 is BACK")]); - - // Then the room is still read - await util.assertStillRead(room2); - }); - test("A reply to a redacted message makes the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a message was redacted - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.assertUnread(room2, 1); - - // And the room is read - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - - // When I receive a reply to the redacted message - await util.receiveMessages(room2, [msg.replyTo("Msg2", "Reply to Msg2")]); - - // Then the room is unread - await util.assertUnread(room2, 1); - }); - test("Reading a reply to a redacted message marks the room as read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given someone replied to a redacted message - await util.goTo(room1); - await util.receiveMessages(room2, ["Msg1", "Msg2"]); - await util.assertUnread(room2, 2); - await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.assertRead(room2); - await util.goTo(room1); - await util.receiveMessages(room2, [msg.replyTo("Msg2", "Reply to Msg2")]); - await util.assertUnread(room2, 1); - - // When I read the reply - await util.goTo(room2); - await util.assertRead(room2); - - // Then the room is unread - await util.goTo(room1); - await util.assertStillRead(room2); - }); - }); - test.describe("in threads", () => { test("Redacting the threaded message pointed to by my receipt leaves the room read", async ({ roomAlpha: room1, @@ -866,214 +558,5 @@ test.describe("Read receipts", () => { await util.assertReadThread("Root"); }); }); - - test.describe("thread roots", () => { - test("Redacting a thread root after it was read leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - test.slow(); - - // Given a thread exists and is read - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Root", - msg.threadedOff("Root", "Msg2"), - msg.threadedOff("Root", "Msg3"), - ]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Root"); - await util.assertRead(room2); - await util.assertReadThread("Root"); - - // When someone redacts the thread root - await util.receiveMessages(room2, [msg.redactionOf("Root")]); - - // Then the room is still read - await util.assertStillRead(room2); - }); - - /* - * Disabled for the same reason as "A thread with a read redaction is still read after restart" - * above - */ - test.skip("Redacting a thread root still allows us to read the thread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given an unread thread exists - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Root", - msg.threadedOff("Root", "Msg2"), - msg.threadedOff("Root", "Msg3"), - ]); - await util.assertUnread(room2, 1); - - // When someone redacts the thread root - await util.receiveMessages(room2, [msg.redactionOf("Root")]); - - // Then the room is still unread - await util.assertUnread(room2, 1); - - // And I can open the thread and read it - await util.goTo(room2); - await util.assertRead(room2); - // The redacted message gets collapsed into, "foo was invited, joined and removed a message" - await util.openCollapsedMessage(1); - await util.openThread("Message deleted"); - await util.assertRead(room2); - await util.assertReadThread("Root"); - }); - - test("Sending a threaded message onto a redacted thread root leaves the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread exists, is read and its root is redacted - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Root", - msg.threadedOff("Root", "Msg2"), - msg.threadedOff("Root", "Msg3"), - ]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Root"); - await util.assertRead(room2); - await util.assertReadThread("Root"); - await util.receiveMessages(room2, [msg.redactionOf("Root")]); - - // When we receive a new message on it - await util.receiveMessages(room2, [msg.threadedOff("Root", "Msg4")]); - - // Then the room is read but the thread is unread - await util.assertRead(room2); - await util.goTo(room2); - await util.assertUnreadThread("Message deleted"); - }); - - test("Reacting to a redacted thread root leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread exists, is read and the root was redacted - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Root", - msg.threadedOff("Root", "Msg2"), - msg.threadedOff("Root", "Msg3"), - ]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Root"); - await util.assertRead(room2); - await util.assertReadThread("Root"); - await util.receiveMessages(room2, [msg.redactionOf("Root")]); - - // When I react to the old root - await util.receiveMessages(room2, [msg.reactionTo("Root", "y")]); - - // Then the room is still read - await util.assertRead(room2); - await util.assertReadThread("Root"); - }); - - test("Editing a redacted thread root leaves the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread exists, is read and the root was redacted - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Root", - msg.threadedOff("Root", "Msg2"), - msg.threadedOff("Root", "Msg3"), - ]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Root"); - await util.assertRead(room2); - await util.assertReadThread("Root"); - await util.receiveMessages(room2, [msg.redactionOf("Root")]); - - // When I edit the old root - await util.receiveMessages(room2, [msg.editOf("Root", "New Root")]); - - // Then the room is still read - await util.assertRead(room2); - // as is the thread - await util.assertReadThread("Root"); - }); - - test("Replying to a redacted thread root makes the room unread", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread exists, is read and the root was redacted - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Root", - msg.threadedOff("Root", "Msg2"), - msg.threadedOff("Root", "Msg3"), - ]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Root"); - await util.assertRead(room2); - await util.assertReadThread("Root"); - await util.receiveMessages(room2, [msg.redactionOf("Root")]); - - // When I reply to the old root - await util.receiveMessages(room2, [msg.replyTo("Root", "Reply!")]); - - // Then the room is unread - await util.assertUnread(room2, 1); - }); - - test("Reading a reply to a redacted thread root makes the room read", async ({ - roomAlpha: room1, - roomBeta: room2, - util, - msg, - }) => { - // Given a thread exists, is read and the root was redacted, and - // someone replied to it - await util.goTo(room1); - await util.receiveMessages(room2, [ - "Root", - msg.threadedOff("Root", "Msg2"), - msg.threadedOff("Root", "Msg3"), - ]); - await util.assertUnread(room2, 1); - await util.goTo(room2); - await util.openThread("Root"); - await util.assertRead(room2); - await util.assertReadThread("Root"); - await util.receiveMessages(room2, [msg.redactionOf("Root")]); - await util.assertStillRead(room2); - await util.receiveMessages(room2, [msg.replyTo("Root", "Reply!")]); - await util.assertUnread(room2, 1); - - // When I read the room - await util.goTo(room2); - - // Then it becomes read - await util.assertRead(room2); - }); - }); }); }); diff --git a/playwright/e2e/read-receipts/redactions-main-timeline.spec.ts b/playwright/e2e/read-receipts/redactions-main-timeline.spec.ts new file mode 100644 index 000000000..cb7393a63 --- /dev/null +++ b/playwright/e2e/read-receipts/redactions-main-timeline.spec.ts @@ -0,0 +1,331 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("redactions", () => { + test.describe("in the main timeline", () => { + test("Redacting the message pointed to by my receipt leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given I have read the messages in a room + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + // When the latest message is redacted + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + + // Then the room remains read + await util.assertStillRead(room2); + }); + + test("Reading an unread room after a redaction of the latest message makes it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an unread room + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + + // And the latest message has been redacted + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + + // When I read the room + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + // Then it becomes read + await util.assertStillRead(room2); + }); + test("Reading an unread room after a redaction of an older message makes it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an unread room with an earlier redaction + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); + + // When I read the room + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + // Then it becomes read + await util.assertStillRead(room2); + }); + test("Marking an unread room as read after a redaction makes it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an unread room where latest message is redacted + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.assertUnread(room2, 1); + + // When I mark it as read + await util.markAsRead(room2); + + // Then it becomes read + await util.assertRead(room2); + }); + test("Sending and redacting a message after marking the room as read makes it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a room that is marked as read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When a message is sent and then redacted + await util.receiveMessages(room2, ["Msg3"]); + await util.assertUnread(room2, 1); + await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); + + // Then the room is read + await util.assertRead(room2); + }); + test("Redacting a message after marking the room as read leaves it read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a room that is marked as read + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2", "Msg3"]); + await util.assertUnread(room2, 3); + await util.markAsRead(room2); + await util.assertRead(room2); + + // When we redact some messages + await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); + await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); + + // Then it is still read + await util.assertStillRead(room2); + }); + test("Redacting one of the unread messages reduces the unread count", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an unread room + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2", "Msg3"]); + await util.assertUnread(room2, 3); + + // When I redact a non-latest message + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + + // Then the unread count goes down + await util.assertUnread(room2, 2); + + // And when I redact the latest message + await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); + + // Then the unread count goes down again + await util.assertUnread(room2, 1); + }); + test("Redacting one of the unread messages reduces the unread count after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given unread count was reduced by redacting messages + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2", "Msg3"]); + await util.assertUnread(room2, 3); + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg3")]); + await util.assertUnread(room2, 1); + + // When I restart + await util.saveAndReload(); + + // Then the unread count is still reduced + await util.assertUnread(room2, 1); + }); + test("Redacting all unread messages makes the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an unread room + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + + // When I redact all the unread messages + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); + + // Then the room is back to being read + await util.assertRead(room2); + }); + test("Redacting all unread messages makes the room read after restart", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given all unread messages were redacted + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.receiveMessages(room2, [msg.redactionOf("Msg1")]); + await util.assertRead(room2); + + // When I restart + await util.saveAndReload(); + + // Then the room is still read + await util.assertRead(room2); + }); + test("Reacting to a redacted message leaves the room read", async ({ + page, + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a redacted message exists + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.assertUnread(room2, 1); + + // And the room is read + await util.goTo(room2); + await util.assertRead(room2); + await page.waitForTimeout(200); + await util.goTo(room1); + + // When I react to the redacted message + await util.receiveMessages(room2, [msg.reactionTo("Msg2", "🪿")]); + + // Then the room is still read + await util.assertStillRead(room2); + }); + test("Editing a redacted message leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a redacted message exists + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.assertUnread(room2, 1); + + // And the room is read + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + // When I attempt to edit the redacted message + await util.receiveMessages(room2, [msg.editOf("Msg2", "Msg2 is BACK")]); + + // Then the room is still read + await util.assertStillRead(room2); + }); + test("A reply to a redacted message makes the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a message was redacted + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.assertUnread(room2, 1); + + // And the room is read + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + + // When I receive a reply to the redacted message + await util.receiveMessages(room2, [msg.replyTo("Msg2", "Reply to Msg2")]); + + // Then the room is unread + await util.assertUnread(room2, 1); + }); + test("Reading a reply to a redacted message marks the room as read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given someone replied to a redacted message + await util.goTo(room1); + await util.receiveMessages(room2, ["Msg1", "Msg2"]); + await util.assertUnread(room2, 2); + await util.receiveMessages(room2, [msg.redactionOf("Msg2")]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.assertRead(room2); + await util.goTo(room1); + await util.receiveMessages(room2, [msg.replyTo("Msg2", "Reply to Msg2")]); + await util.assertUnread(room2, 1); + + // When I read the reply + await util.goTo(room2); + await util.assertRead(room2); + + // Then the room is unread + await util.goTo(room1); + await util.assertStillRead(room2); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/redactions-thread-roots.spec.ts b/playwright/e2e/read-receipts/redactions-thread-roots.spec.ts new file mode 100644 index 000000000..0ded3957f --- /dev/null +++ b/playwright/e2e/read-receipts/redactions-thread-roots.spec.ts @@ -0,0 +1,232 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("redactions", () => { + test.describe("thread roots", () => { + test("Redacting a thread root after it was read leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + test.slow(); + + // Given a thread exists and is read + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Root", + msg.threadedOff("Root", "Msg2"), + msg.threadedOff("Root", "Msg3"), + ]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Root"); + await util.assertRead(room2); + await util.assertReadThread("Root"); + + // When someone redacts the thread root + await util.receiveMessages(room2, [msg.redactionOf("Root")]); + + // Then the room is still read + await util.assertStillRead(room2); + }); + + /* + * Disabled for the same reason as "A thread with a read redaction is still read after restart" + * above + */ + test.skip("Redacting a thread root still allows us to read the thread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given an unread thread exists + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Root", + msg.threadedOff("Root", "Msg2"), + msg.threadedOff("Root", "Msg3"), + ]); + await util.assertUnread(room2, 1); + + // When someone redacts the thread root + await util.receiveMessages(room2, [msg.redactionOf("Root")]); + + // Then the room is still unread + await util.assertUnread(room2, 1); + + // And I can open the thread and read it + await util.goTo(room2); + await util.assertRead(room2); + // The redacted message gets collapsed into, "foo was invited, joined and removed a message" + await util.openCollapsedMessage(1); + await util.openThread("Message deleted"); + await util.assertRead(room2); + await util.assertReadThread("Root"); + }); + + test("Sending a threaded message onto a redacted thread root leaves the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread exists, is read and its root is redacted + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Root", + msg.threadedOff("Root", "Msg2"), + msg.threadedOff("Root", "Msg3"), + ]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Root"); + await util.assertRead(room2); + await util.assertReadThread("Root"); + await util.receiveMessages(room2, [msg.redactionOf("Root")]); + + // When we receive a new message on it + await util.receiveMessages(room2, [msg.threadedOff("Root", "Msg4")]); + + // Then the room is read but the thread is unread + await util.assertRead(room2); + await util.goTo(room2); + await util.assertUnreadThread("Message deleted"); + }); + + test("Reacting to a redacted thread root leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread exists, is read and the root was redacted + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Root", + msg.threadedOff("Root", "Msg2"), + msg.threadedOff("Root", "Msg3"), + ]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Root"); + await util.assertRead(room2); + await util.assertReadThread("Root"); + await util.receiveMessages(room2, [msg.redactionOf("Root")]); + + // When I react to the old root + await util.receiveMessages(room2, [msg.reactionTo("Root", "y")]); + + // Then the room is still read + await util.assertRead(room2); + await util.assertReadThread("Root"); + }); + + test("Editing a redacted thread root leaves the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread exists, is read and the root was redacted + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Root", + msg.threadedOff("Root", "Msg2"), + msg.threadedOff("Root", "Msg3"), + ]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Root"); + await util.assertRead(room2); + await util.assertReadThread("Root"); + await util.receiveMessages(room2, [msg.redactionOf("Root")]); + + // When I edit the old root + await util.receiveMessages(room2, [msg.editOf("Root", "New Root")]); + + // Then the room is still read + await util.assertRead(room2); + // as is the thread + await util.assertReadThread("Root"); + }); + + test("Replying to a redacted thread root makes the room unread", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread exists, is read and the root was redacted + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Root", + msg.threadedOff("Root", "Msg2"), + msg.threadedOff("Root", "Msg3"), + ]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Root"); + await util.assertRead(room2); + await util.assertReadThread("Root"); + await util.receiveMessages(room2, [msg.redactionOf("Root")]); + + // When I reply to the old root + await util.receiveMessages(room2, [msg.replyTo("Root", "Reply!")]); + + // Then the room is unread + await util.assertUnread(room2, 1); + }); + + test("Reading a reply to a redacted thread root makes the room read", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + // Given a thread exists, is read and the root was redacted, and + // someone replied to it + await util.goTo(room1); + await util.receiveMessages(room2, [ + "Root", + msg.threadedOff("Root", "Msg2"), + msg.threadedOff("Root", "Msg3"), + ]); + await util.assertUnread(room2, 1); + await util.goTo(room2); + await util.openThread("Root"); + await util.assertRead(room2); + await util.assertReadThread("Root"); + await util.receiveMessages(room2, [msg.redactionOf("Root")]); + await util.assertStillRead(room2); + await util.receiveMessages(room2, [msg.replyTo("Root", "Reply!")]); + await util.assertUnread(room2, 1); + + // When I read the room + await util.goTo(room2); + + // Then it becomes read + await util.assertRead(room2); + }); + }); + }); +}); diff --git a/playwright/e2e/read-receipts/room-list-order.spec.ts b/playwright/e2e/read-receipts/room-list-order.spec.ts new file mode 100644 index 000000000..2b4302291 --- /dev/null +++ b/playwright/e2e/read-receipts/room-list-order.spec.ts @@ -0,0 +1,61 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +/* See readme.md for tips on writing these tests. */ + +import { test } from "."; + +test.describe("Read receipts", () => { + test.describe("Room list order", () => { + test("Rooms with unread messages appear at the top of room list if 'unread first' is selected", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + page, + }) => { + await util.goTo(room2); + + // Display the unread first room + await util.toggleRoomUnreadOrder(); + await util.receiveMessages(room1, ["Msg1"]); + await page.reload(); + + // Room 1 has an unread message and should be displayed first + await util.assertRoomListOrder([room1, room2]); + }); + + test("Rooms with unread threads appear at the top of room list if 'unread first' is selected", async ({ + roomAlpha: room1, + roomBeta: room2, + util, + msg, + }) => { + await util.goTo(room2); + await util.receiveMessages(room1, ["Msg1"]); + await util.markAsRead(room1); + await util.assertRead(room1); + + // Display the unread first room + await util.toggleRoomUnreadOrder(); + await util.receiveMessages(room1, [msg.threadedOff("Msg1", "Resp1")]); + await util.saveAndReload(); + + // Room 1 has an unread message and should be displayed first + await util.assertRoomListOrder([room1, room2]); + }); + }); +}); diff --git a/playwright/e2e/right-panel/file-panel.spec.ts b/playwright/e2e/right-panel/file-panel.spec.ts index 84e7614e8..52dd11331 100644 --- a/playwright/e2e/right-panel/file-panel.spec.ts +++ b/playwright/e2e/right-panel/file-panel.spec.ts @@ -50,7 +50,7 @@ test.describe("FilePanel", () => { test.describe("render", () => { test("should render empty state", async ({ page }) => { // Wait until the information about the empty state is rendered - await expect(page.locator(".mx_FilePanel_empty")).toBeVisible(); + await expect(page.locator(".mx_EmptyState")).toBeVisible(); // Take a snapshot of RightPanel - fix https://github.com/vector-im/element-web/issues/25332 await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png"); diff --git a/playwright/e2e/right-panel/notification-panel.spec.ts b/playwright/e2e/right-panel/notification-panel.spec.ts index 6223c1c13..aa7dedf73 100644 --- a/playwright/e2e/right-panel/notification-panel.spec.ts +++ b/playwright/e2e/right-panel/notification-panel.spec.ts @@ -35,7 +35,7 @@ test.describe("NotificationPanel", () => { await page.getByRole("button", { name: "Notifications" }).click(); // Wait until the information about the empty state is rendered - await expect(page.locator(".mx_NotificationPanel_empty")).toBeVisible(); + await expect(page.locator(".mx_EmptyState")).toBeVisible(); // Take a snapshot of RightPanel await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png"); diff --git a/playwright/e2e/right-panel/right-panel.spec.ts b/playwright/e2e/right-panel/right-panel.spec.ts index 4f578748d..f282d83d6 100644 --- a/playwright/e2e/right-panel/right-panel.spec.ts +++ b/playwright/e2e/right-panel/right-panel.spec.ts @@ -63,9 +63,9 @@ test.describe("RightPanel", () => { await app.closeDialog(); // Close and reopen the right panel to render the room address - await page.getByRole("button", { name: "Room info" }).click(); + await app.toggleRoomInfoPanel(); await expect(page.locator(".mx_RightPanel")).not.toBeVisible(); - await page.getByRole("button", { name: "Room info" }).click(); + await app.toggleRoomInfoPanel(); await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-name-and-address.png"); }); @@ -104,26 +104,26 @@ test.describe("RightPanel", () => { await page.getByRole("menuitem", { name: "Files" }).click(); await expect(page.locator(".mx_FilePanel")).toBeVisible(); - await expect(page.locator(".mx_FilePanel_empty")).toBeVisible(); + await expect(page.locator(".mx_EmptyState")).toBeVisible(); - await page.getByRole("button", { name: "Room information" }).click(); + await page.getByTestId("base-card-back-button").click(); await checkRoomSummaryCard(page, ROOM_NAME); }); test("should handle viewing room member", async ({ page, app }) => { await viewRoomSummaryByName(page, app, ROOM_NAME); - await page.getByRole("menuitem", { name: "People" }).click(); + await page.locator(".mx_RightPanelTabs").getByText("People").click(); await expect(page.locator(".mx_MemberList")).toBeVisible(); await getMemberTileByName(page, NAME).click(); await expect(page.locator(".mx_UserInfo")).toBeVisible(); await expect(page.locator(".mx_UserInfo_profile").getByText(NAME)).toBeVisible(); - await page.getByRole("button", { name: "Room members" }).click(); + await page.getByTestId("base-card-back-button").click(); await expect(page.locator(".mx_MemberList")).toBeVisible(); - await page.getByRole("button", { name: "Room information" }).click(); + await page.locator(".mx_RightPanelTabs").getByText("Info").click(); await checkRoomSummaryCard(page, ROOM_NAME); }); }); @@ -138,14 +138,12 @@ test.describe("RightPanel", () => { .getByRole("button", { name: /\d member/ }) .click(); await expect(page.locator(".mx_MemberList")).toBeVisible(); - await expect(page.locator(".mx_SpaceScopeHeader").getByText(SPACE_NAME)).toBeVisible(); await getMemberTileByName(page, NAME).click(); await expect(page.locator(".mx_UserInfo")).toBeVisible(); await expect(page.locator(".mx_UserInfo_profile").getByText(NAME)).toBeVisible(); - await expect(page.locator(".mx_SpaceScopeHeader").getByText(SPACE_NAME)).toBeVisible(); - await page.getByRole("button", { name: "Back" }).click(); + await page.getByTestId("base-card-back-button").click(); await expect(page.locator(".mx_MemberList")).toBeVisible(); }); }); diff --git a/playwright/e2e/right-panel/utils.ts b/playwright/e2e/right-panel/utils.ts index a8dac8394..5e2e39be0 100644 --- a/playwright/e2e/right-panel/utils.ts +++ b/playwright/e2e/right-panel/utils.ts @@ -20,7 +20,7 @@ import { ElementAppPage } from "../../pages/ElementAppPage"; export async function viewRoomSummaryByName(page: Page, app: ElementAppPage, name: string): Promise { await app.viewRoomByName(name); - await page.getByRole("button", { name: "Room info" }).click(); + await app.toggleRoomInfoPanel(); return checkRoomSummaryCard(page, name); } diff --git a/playwright/e2e/room/room-header.spec.ts b/playwright/e2e/room/room-header.spec.ts index 4008517d0..ca49f1190 100644 --- a/playwright/e2e/room/room-header.spec.ts +++ b/playwright/e2e/room/room-header.spec.ts @@ -18,7 +18,6 @@ import { Page } from "@playwright/test"; import { test, expect } from "../../element-web-test"; import { ElementAppPage } from "../../pages/ElementAppPage"; -import type { Container } from "../../../src/stores/widgets/types"; test.describe("Room Header", () => { test.use({ @@ -33,24 +32,28 @@ test.describe("Room Header", () => { await app.client.createRoom({ name: "Test Room" }); await app.viewRoomByName("Test Room"); - const header = page.locator(".mx_LegacyRoomHeader"); - // Names (aria-label) of every button rendered on mx_LegacyRoomHeader by default - const expectedButtonNames = [ - "Room options", // The room name button next to the room avatar, which renders dropdown menu on click - "Voice call", - "Video call", - "Search", - "Threads", - "Notifications", - "Room info", - ]; - - // Assert they are found and visible - for (const name of expectedButtonNames) { - await expect(header.getByRole("button", { name })).toBeVisible(); - } + const header = page.locator(".mx_RoomHeader"); + + // There's two room info button - the header itself and the i button + const infoButtons = header.getByRole("button", { name: "Room info" }); + await expect(infoButtons).toHaveCount(2); + await expect(infoButtons.first()).toBeVisible(); + await expect(infoButtons.last()).toBeVisible(); + + // Memberlist button + await expect(header.locator(".mx_FacePile")).toBeVisible(); - // Assert that just those seven buttons exist on mx_LegacyRoomHeader by default + // There should be both a voice and a video call button + // but they'll be disabled + const callButtons = header.getByRole("button", { name: "There's no one here to call" }); + await expect(callButtons).toHaveCount(2); + await expect(callButtons.first()).toBeVisible(); + await expect(callButtons.last()).toBeVisible(); + + await expect(header.getByRole("button", { name: "Threads" })).toBeVisible(); + await expect(header.getByRole("button", { name: "Notifications" })).toBeVisible(); + + // Assert that there are six buttons in total await expect(header.getByRole("button")).toHaveCount(7); await expect(header).toMatchScreenshot("room-header.png"); @@ -67,14 +70,15 @@ test.describe("Room Header", () => { await app.client.createRoom({ name: LONG_ROOM_NAME }); await app.viewRoomByName(LONG_ROOM_NAME); - const header = page.locator(".mx_LegacyRoomHeader"); + const header = page.locator(".mx_RoomHeader"); // Wait until the room name is set - await expect(page.locator(".mx_LegacyRoomHeader_nametext").getByText(LONG_ROOM_NAME)).toBeVisible(); + await expect(page.locator(".mx_RoomHeader_heading").getByText(LONG_ROOM_NAME)).toBeVisible(); // Assert the size of buttons on RoomHeader are specified and the buttons are not compressed // Note these assertions do not check the size of mx_LegacyRoomHeader_name button - const buttons = page.locator(".mx_LegacyRoomHeader_button"); - await expect(buttons).toHaveCount(6); + const buttons = header.locator(".mx_Flex").getByRole("button"); + await expect(buttons).toHaveCount(5); + for (const button of await buttons.all()) { await expect(button).toBeVisible(); await expect(button).toHaveCSS("height", "32px"); @@ -83,44 +87,6 @@ test.describe("Room Header", () => { await expect(header).toMatchScreenshot("room-header-long-name.png"); }); - - test("should have buttons highlighted by being clicked", async ({ page, app, user }) => { - await app.client.createRoom({ name: "Test Room" }); - await app.viewRoomByName("Test Room"); - - const header = page.locator(".mx_LegacyRoomHeader"); - // Check these buttons - const buttonsHighlighted = ["Threads", "Notifications", "Room info"]; - - for (const name of buttonsHighlighted) { - await header.getByRole("button", { name: name }).click(); // Highlight the button - } - - await expect(header).toMatchScreenshot("room-header-highlighted.png"); - }); - }); - - test.describe("with feature_pinning enabled", () => { - test.use({ labsFlags: ["feature_pinning"] }); - - test("should render the pin button for pinned messages card", async ({ page, app, user }) => { - await app.client.createRoom({ name: "Test Room" }); - await app.viewRoomByName("Test Room"); - - const composer = app.getComposer().locator("[contenteditable]"); - await composer.fill("Test message"); - await composer.press("Enter"); - - const lastTile = page.locator(".mx_EventTile_last"); - await lastTile.hover(); - await lastTile.getByRole("button", { name: "Options" }).click(); - - await page.getByRole("menuitem", { name: "Pin" }).click(); - - await expect( - page.locator(".mx_LegacyRoomHeader").getByRole("button", { name: "Pinned messages" }), - ).toBeVisible(); - }); }); test.describe("with a video room", () => { @@ -141,30 +107,27 @@ test.describe("Room Header", () => { test.describe("and with feature_notifications enabled", () => { test.use({ labsFlags: ["feature_video_rooms", "feature_notifications"] }); - test("should render buttons for room options, beta pill, invite, chat, and room info", async ({ - page, - app, - user, - }) => { + test("should render buttons for chat, room info, threads and facepile", async ({ page, app, user }) => { await createVideoRoom(page, app); - const header = page.locator(".mx_LegacyRoomHeader"); - // Names (aria-label) of the buttons on the video room header - const expectedButtonNames = [ - "Room options", - "Video rooms are a beta feature Click for more info", // Beta pill - "Invite", - "Chat", - "Room info", - ]; - - // Assert they are found and visible - for (const name of expectedButtonNames) { - await expect(header.getByRole("button", { name })).toBeVisible(); - } + const header = page.locator(".mx_RoomHeader"); + + // There's two room info button - the header itself and the i button + const infoButtons = header.getByRole("button", { name: "Room info" }); + await expect(infoButtons).toHaveCount(2); + await expect(infoButtons.first()).toBeVisible(); + await expect(infoButtons.last()).toBeVisible(); + + // Facepile + await expect(header.locator(".mx_FacePile")).toBeVisible(); + + // Chat, Threads and Notification buttons + await expect(header.getByRole("button", { name: "Chat" })).toBeVisible(); + await expect(header.getByRole("button", { name: "Threads" })).toBeVisible(); + await expect(header.getByRole("button", { name: "Notifications" })).toBeVisible(); // Assert that there is not a button except those buttons - await expect(header.getByRole("button")).toHaveCount(7); + await expect(header.getByRole("button")).toHaveCount(6); await expect(header).toMatchScreenshot("room-header-video-room.png"); }); @@ -177,7 +140,7 @@ test.describe("Room Header", () => { }) => { await createVideoRoom(page, app); - await page.locator(".mx_LegacyRoomHeader").getByRole("button", { name: "Chat" }).click(); + await page.locator(".mx_RoomHeader").getByRole("button", { name: "Chat" }).click(); // Assert that the call view is still visible await expect(page.locator(".mx_CallView")).toBeVisible(); @@ -188,114 +151,4 @@ test.describe("Room Header", () => { ).toBeVisible(); }); }); - - test.describe("with a widget", () => { - const ROOM_NAME = "Test Room with a widget"; - const WIDGET_ID = "fake-widget"; - const WIDGET_HTML = ` - - - Fake Widget - - - Hello World - - - `; - - test.beforeEach(async ({ page, app, user, webserver }) => { - const widgetUrl = webserver.start(WIDGET_HTML); - const roomId = await app.client.createRoom({ name: ROOM_NAME }); - - // setup widget via state event - await app.client.evaluate( - async (matrixClient, { roomId, widgetUrl, id }) => { - await matrixClient.sendStateEvent( - roomId, - "im.vector.modular.widgets", - { - id, - creatorUserId: "somebody", - type: "widget", - name: "widget", - url: widgetUrl, - }, - id, - ); - await matrixClient.sendStateEvent( - roomId, - "io.element.widgets.layout", - { - widgets: { - [id]: { - container: "top" as Container, - index: 1, - width: 100, - height: 0, - }, - }, - }, - "", - ); - }, - { - roomId, - widgetUrl, - id: WIDGET_ID, - }, - ); - - // open the room - await app.viewRoomByName(ROOM_NAME); - }); - - test("should highlight the apps button", async ({ page, app, user }) => { - // Assert that AppsDrawer is rendered - await expect(page.locator(".mx_AppsDrawer")).toBeVisible(); - - const header = page.locator(".mx_LegacyRoomHeader"); - // Assert that "Hide Widgets" button is rendered and aria-checked is set to true - await expect(header.getByRole("button", { name: "Hide Widgets" })).toHaveAttribute("aria-checked", "true"); - - await expect(header).toMatchScreenshot("room-header-with-apps-button-highlighted.png"); - }); - - test("should support hiding a widget", async ({ page, app, user }) => { - await expect(page.locator(".mx_AppsDrawer")).toBeVisible(); - - const header = page.locator(".mx_LegacyRoomHeader"); - // Click the apps button to hide AppsDrawer - await header.getByRole("button", { name: "Hide Widgets" }).click(); - - // Assert that "Show widgets" button is rendered and aria-checked is set to false - await expect(header.getByRole("button", { name: "Show Widgets" })).toHaveAttribute("aria-checked", "false"); - - // Assert that AppsDrawer is not rendered - await expect(page.locator(".mx_AppsDrawer")).not.toBeVisible(); - - await expect(header).toMatchScreenshot("room-header-with-apps-button-not-highlighted.png"); - }); - }); - - test.describe("with encryption", () => { - test("should render the E2E icon and the buttons", async ({ page, app, user }) => { - // Create an encrypted room - await app.client.createRoom({ - name: "Test Encrypted Room", - initial_state: [ - { - type: "m.room.encryption", - state_key: "", - content: { - algorithm: "m.megolm.v1.aes-sha2", - }, - }, - ], - }); - await app.viewRoomByName("Test Encrypted Room"); - - const header = page.locator(".mx_LegacyRoomHeader"); - await expect(header).toMatchScreenshot("encrypted-room-header.png"); - }); - }); }); diff --git a/playwright/e2e/settings/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab.spec.ts deleted file mode 100644 index 7e16d7395..000000000 --- a/playwright/e2e/settings/appearance-user-settings-tab.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -/* -Copyright 2023 Suguru Hirahara - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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 { test, expect } from "../../element-web-test"; -import { SettingLevel } from "../../../src/settings/SettingLevel"; - -test.describe("Appearance user settings tab", () => { - test.use({ - displayName: "Hanako", - }); - - test("should be rendered properly", async ({ page, user, app }) => { - const tab = await app.settings.openUserSettings("Appearance"); - - // Click "Show advanced" link button - await tab.getByRole("button", { name: "Show advanced" }).click(); - - // Assert that "Hide advanced" link button is rendered - await expect(tab.getByRole("button", { name: "Hide advanced" })).toBeVisible(); - - await expect(tab).toMatchScreenshot("appearance-tab.png"); - }); - - test("should support switching layouts", async ({ page, user, app }) => { - // Create and view a room first - await app.client.createRoom({ name: "Test Room" }); - await app.viewRoomByName("Test Room"); - - await app.settings.openUserSettings("Appearance"); - - const buttons = page.locator(".mx_LayoutSwitcher_RadioButton"); - - // Assert that the layout selected by default is "Modern" - await expect( - buttons.locator(".mx_StyledRadioButton_enabled", { - hasText: "Modern", - }), - ).toBeVisible(); - - // Assert that the room layout is set to group (modern) layout - await expect(page.locator(".mx_RoomView_body[data-layout='group']")).toBeVisible(); - - // Select the first layout - await buttons.first().click(); - // Assert that the layout selected is "IRC (Experimental)" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "IRC (Experimental)" })).toBeVisible(); - - // Assert that the room layout is set to IRC layout - await expect(page.locator(".mx_RoomView_body[data-layout='irc']")).toBeVisible(); - - // Select the last layout - await buttons.last().click(); - - // Assert that the layout selected is "Message bubbles" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "Message bubbles" })).toBeVisible(); - - // Assert that the room layout is set to bubble layout - await expect(page.locator(".mx_RoomView_body[data-layout='bubble']")).toBeVisible(); - }); - - test("should support changing font size by using the font size dropdown", async ({ page, app, user }) => { - await app.settings.openUserSettings("Appearance"); - - const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); - const fontDropdown = tab.locator(".mx_FontScalingPanel_Dropdown"); - await expect(fontDropdown.getByLabel("Font size")).toBeVisible(); - - // Default browser font size is 16px and the select value is 0 - // -4 value is 12px - await fontDropdown.getByLabel("Font size").selectOption({ value: "-4" }); - - await expect(page).toMatchScreenshot("window-12px.png"); - }); - - test("should support enabling compact group (modern) layout", async ({ page, app, user }) => { - // Create and view a room first - await app.client.createRoom({ name: "Test Room" }); - await app.viewRoomByName("Test Room"); - - await app.settings.openUserSettings("Appearance"); - - // Click "Show advanced" link button - const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); - await tab.getByRole("button", { name: "Show advanced" }).click(); - - await tab.locator("label", { hasText: "Use a more compact 'Modern' layout" }).click(); - - // Assert that the room layout is set to compact group (modern) layout - await expect(page.locator("#matrixchat .mx_MatrixChat_wrapper.mx_MatrixChat_useCompactLayout")).toBeVisible(); - }); - - test("should disable compact group (modern) layout option on IRC layout and bubble layout", async ({ - page, - app, - user, - }) => { - await app.settings.openUserSettings("Appearance"); - const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); - - const checkDisabled = async () => { - await expect(tab.getByRole("checkbox", { name: "Use a more compact 'Modern' layout" })).toBeDisabled(); - }; - - // Click "Show advanced" link button - await tab.getByRole("button", { name: "Show advanced" }).click(); - - const buttons = page.locator(".mx_LayoutSwitcher_RadioButton"); - - // Enable IRC layout - await buttons.first().click(); - - // Assert that the layout selected is "IRC (Experimental)" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "IRC (Experimental)" })).toBeVisible(); - - await checkDisabled(); - - // Enable bubble layout - await buttons.last().click(); - - // Assert that the layout selected is "IRC (Experimental)" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "Message bubbles" })).toBeVisible(); - - await checkDisabled(); - }); - - test("should support enabling system font", async ({ page, app, user }) => { - await app.settings.openUserSettings("Appearance"); - const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); - - // Click "Show advanced" link button - await tab.getByRole("button", { name: "Show advanced" }).click(); - - await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click(); - await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click(); - - // Assert that the font-family value was removed - await expect(page.locator("body")).toHaveCSS("font-family", '""'); - }); - - test.describe("Theme Choice Panel", () => { - test.beforeEach(async ({ app, user }) => { - // Disable the default theme for consistency in case ThemeWatcher automatically chooses it - await app.settings.setValue("use_system_theme", null, SettingLevel.DEVICE, false); - }); - - test("should be rendered with the light theme selected", async ({ page, app }) => { - await app.settings.openUserSettings("Appearance"); - const themePanel = page.getByTestId("mx_ThemeChoicePanel"); - - const useSystemTheme = themePanel.getByTestId("checkbox-use-system-theme"); - await expect(useSystemTheme.getByText("Match system theme")).toBeVisible(); - // Assert that 'Match system theme' is not checked - // Note that mx_Checkbox_checkmark exists and is hidden by CSS if it is not checked - await expect(useSystemTheme.locator(".mx_Checkbox_checkmark")).not.toBeVisible(); - - const selectors = themePanel.getByTestId("theme-choice-panel-selectors"); - await expect(selectors.locator(".mx_ThemeSelector_light")).toBeVisible(); - await expect(selectors.locator(".mx_ThemeSelector_dark")).toBeVisible(); - // Assert that the light theme is selected - await expect(selectors.locator(".mx_ThemeSelector_light.mx_StyledRadioButton_enabled")).toBeVisible(); - // Assert that the buttons for the light and dark theme are not enabled - await expect(selectors.locator(".mx_ThemeSelector_light.mx_StyledRadioButton_disabled")).not.toBeVisible(); - await expect(selectors.locator(".mx_ThemeSelector_dark.mx_StyledRadioButton_disabled")).not.toBeVisible(); - - // Assert that the checkbox for the high contrast theme is rendered - await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).toBeVisible(); - }); - - test("should disable the labels for themes and the checkbox for the high contrast theme if the checkbox for the system theme is clicked", async ({ - page, - app, - }) => { - await app.settings.openUserSettings("Appearance"); - const themePanel = page.getByTestId("mx_ThemeChoicePanel"); - - await themePanel.locator(".mx_Checkbox", { hasText: "Match system theme" }).click(); - - // Assert that the labels for the light theme and dark theme are disabled - await expect(themePanel.locator(".mx_ThemeSelector_light.mx_StyledRadioButton_disabled")).toBeVisible(); - await expect(themePanel.locator(".mx_ThemeSelector_dark.mx_StyledRadioButton_disabled")).toBeVisible(); - - // Assert that there does not exist a label for an enabled theme - await expect(themePanel.locator("label.mx_StyledRadioButton_enabled")).not.toBeVisible(); - - // Assert that the checkbox and label to enable the high contrast theme should not exist - await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).not.toBeVisible(); - }); - - test("should not render the checkbox and the label for the high contrast theme if the dark theme is selected", async ({ - page, - app, - }) => { - await app.settings.openUserSettings("Appearance"); - const themePanel = page.getByTestId("mx_ThemeChoicePanel"); - - // Assert that the checkbox and the label to enable the high contrast theme should exist - await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).toBeVisible(); - - // Enable the dark theme - await themePanel.locator(".mx_ThemeSelector_dark").click(); - - // Assert that the checkbox and the label should not exist - await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).not.toBeVisible(); - }); - }); -}); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts new file mode 100644 index 000000000..aa00681f6 --- /dev/null +++ b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts @@ -0,0 +1,63 @@ +/* +Copyright 2023 Suguru Hirahara + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { expect, test } from "."; + +test.describe("Appearance user settings tab", () => { + test.use({ + displayName: "Hanako", + }); + + test("should be rendered properly", async ({ page, user, app }) => { + const tab = await app.settings.openUserSettings("Appearance"); + + // Click "Show advanced" link button + await tab.getByRole("button", { name: "Show advanced" }).click(); + + // Assert that "Hide advanced" link button is rendered + await expect(tab.getByRole("button", { name: "Hide advanced" })).toBeVisible(); + + await expect(tab).toMatchScreenshot("appearance-tab.png"); + }); + + test("should support changing font size by using the font size dropdown", async ({ page, app, user }) => { + await app.settings.openUserSettings("Appearance"); + + const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); + const fontDropdown = tab.locator(".mx_FontScalingPanel_Dropdown"); + await expect(fontDropdown.getByLabel("Font size")).toBeVisible(); + + // Default browser font size is 16px and the select value is 0 + // -4 value is 12px + await fontDropdown.getByLabel("Font size").selectOption({ value: "-4" }); + + await expect(page).toMatchScreenshot("window-12px.png"); + }); + + test("should support enabling system font", async ({ page, app, user }) => { + await app.settings.openUserSettings("Appearance"); + const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); + + // Click "Show advanced" link button + await tab.getByRole("button", { name: "Show advanced" }).click(); + + await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click(); + await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click(); + + // Assert that the font-family value was removed + await expect(page.locator("body")).toHaveCSS("font-family", '""'); + }); +}); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/index.ts b/playwright/e2e/settings/appearance-user-settings-tab/index.ts new file mode 100644 index 000000000..e8641306e --- /dev/null +++ b/playwright/e2e/settings/appearance-user-settings-tab/index.ts @@ -0,0 +1,241 @@ +/* + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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 { Locator, Page } from "@playwright/test"; + +import { ElementAppPage } from "../../../pages/ElementAppPage"; +import { test as base, expect } from "../../../element-web-test"; +import { SettingLevel } from "../../../../src/settings/SettingLevel"; +import { Layout } from "../../../../src/settings/enums/Layout"; + +export { expect }; + +/** + * Set up for the appearance tab test + */ +export const test = base.extend<{ + util: Helpers; +}>({ + util: async ({ page, app }, use) => { + await use(new Helpers(page, app)); + }, +}); + +/** + * A collection of helper functions for the appearance tab test + * The goal is to make easier to get and interact with the button, input, or other elements of the appearance tab + */ +class Helpers { + private CUSTOM_THEME_URL = "http://custom.theme"; + private CUSTOM_THEME = { + name: "Custom theme", + isDark: false, + colors: {}, + }; + + constructor( + private page: Page, + private app: ElementAppPage, + ) {} + + /** + * Open the appearance tab + */ + openAppearanceTab() { + return this.app.settings.openUserSettings("Appearance"); + } + + /** + * Compare screenshot and hide the matrix chat + * @param locator + * @param screenshot + */ + assertScreenshot(locator: Locator, screenshot: `${string}.png`) { + return expect(locator).toMatchScreenshot(screenshot, { + css: ` + #matrixchat { + display: none; + } + `, + }); + } + + // Theme Panel + + /** + * Disable in the settings the system theme + */ + disableSystemTheme() { + return this.app.settings.setValue("use_system_theme", null, SettingLevel.DEVICE, false); + } + + /** + * Return the theme section + */ + getThemePanel() { + return this.page.getByTestId("themePanel"); + } + + /** + * Return the system theme toggle + */ + getMatchSystemThemeCheckbox() { + return this.getThemePanel().getByRole("checkbox", { name: "Match system theme" }); + } + + /** + * Return the theme radio button + * @param theme - the theme to select + * @private + */ + private getThemeRadio(theme: string) { + return this.getThemePanel().getByRole("radio", { name: theme }); + } + + /** + * Return the light theme radio button + */ + getLightTheme() { + return this.getThemeRadio("Light"); + } + + /** + * Return the dark theme radio button + */ + getDarkTheme() { + return this.getThemeRadio("Dark"); + } + + /** + * Return the custom theme radio button + */ + getCustomTheme() { + return this.getThemeRadio(this.CUSTOM_THEME.name); + } + + /** + * Return the high contrast theme radio button + */ + getHighContrastTheme() { + return this.getThemeRadio("High contrast"); + } + + /** + * Add a custom theme + * Mock the request to the custom and return a fake local custom theme + */ + async addCustomTheme() { + await this.page.route(this.CUSTOM_THEME_URL, (route) => + route.fulfill({ body: JSON.stringify(this.CUSTOM_THEME) }), + ); + await this.page.getByRole("textbox", { name: "Add custom theme" }).fill(this.CUSTOM_THEME_URL); + await this.page.getByRole("button", { name: "Add custom theme" }).click(); + await this.page.unroute(this.CUSTOM_THEME_URL); + } + + /** + * Remove the custom theme + */ + removeCustomTheme() { + return this.getThemePanel().getByRole("listitem", { name: this.CUSTOM_THEME.name }).getByRole("button").click(); + } + + // Message layout Panel + + /** + * Create and display a room named Test Room + */ + async createAndDisplayRoom() { + await this.app.client.createRoom({ name: "Test Room" }); + await this.app.viewRoomByName("Test Room"); + } + + /** + * Assert the room layout + * @param layout + * @private + */ + private assertRoomLayout(layout: Layout) { + return expect(this.page.locator(`.mx_RoomView_body[data-layout=${layout}]`)).toBeVisible(); + } + + /** + * Assert the room layout is modern + */ + assertModernLayout() { + return this.assertRoomLayout(Layout.Group); + } + + /** + * Assert the room layout is bubble + */ + assertBubbleLayout() { + return this.assertRoomLayout(Layout.Bubble); + } + + /** + * Return the layout panel + */ + getMessageLayoutPanel() { + return this.page.getByTestId("layoutPanel"); + } + + /** + * Return the layout radio button + * @param layoutName + * @private + */ + private getLayout(layoutName: string) { + return this.getMessageLayoutPanel().getByRole("radio", { name: layoutName }); + } + + /** + * Return the message bubbles layout radio button + */ + getBubbleLayout() { + return this.getLayout("Message bubbles"); + } + + /** + * Return the modern layout radio button + */ + getModernLayout() { + return this.getLayout("Modern"); + } + + /** + * Return the IRC layout radio button + */ + getIRCLayout() { + return this.getLayout("IRC (experimental)"); + } + + /** + * Return the compact layout checkbox + */ + getCompactLayoutCheckbox() { + return this.getMessageLayoutPanel().getByRole("checkbox", { name: "Show compact text and messages" }); + } + + /** + * Assert the compact layout is enabled + */ + assertCompactLayout() { + return expect( + this.page.locator("#matrixchat .mx_MatrixChat_wrapper.mx_MatrixChat_useCompactLayout"), + ).toBeVisible(); + } +} diff --git a/playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.ts b/playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.ts new file mode 100644 index 000000000..1a22696da --- /dev/null +++ b/playwright/e2e/settings/appearance-user-settings-tab/message-layout-panel.ts @@ -0,0 +1,66 @@ +/* +Copyright 2023 Suguru Hirahara + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { expect, test } from "."; + +test.describe("Appearance user settings tab", () => { + test.use({ + displayName: "Hanako", + }); + + test.describe("Message Layout Panel", () => { + test.beforeEach(async ({ app, user, util }) => { + await util.createAndDisplayRoom(); + await util.assertModernLayout(); + await util.openAppearanceTab(); + }); + + test("should change the message layout from modern to bubble", async ({ page, app, user, util }) => { + await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-modern.png"); + + await util.getBubbleLayout().click(); + + // Assert that modern are irc layout are not selected + await expect(util.getBubbleLayout()).toBeChecked(); + await expect(util.getModernLayout()).not.toBeChecked(); + await expect(util.getIRCLayout()).not.toBeChecked(); + + // Assert that the room layout is set to bubble layout + await util.assertBubbleLayout(); + await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-bubble.png"); + }); + + test("should enable compact layout when the modern layout is selected", async ({ page, app, user, util }) => { + await expect(util.getCompactLayoutCheckbox()).not.toBeChecked(); + + await util.getCompactLayoutCheckbox().click(); + await util.assertCompactLayout(); + }); + + test("should disable compact layout when the modern layout is not selected", async ({ + page, + app, + user, + util, + }) => { + await expect(util.getCompactLayoutCheckbox()).not.toBeDisabled(); + + // Select the bubble layout, which should disable the compact layout checkbox + await util.getBubbleLayout().click(); + await expect(util.getCompactLayoutCheckbox()).toBeDisabled(); + }); + }); +}); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts new file mode 100644 index 000000000..2b1e8cc14 --- /dev/null +++ b/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts @@ -0,0 +1,89 @@ +/* +Copyright 2023 Suguru Hirahara + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 { expect, test } from "."; + +test.describe("Appearance user settings tab", () => { + test.use({ + displayName: "Hanako", + }); + + test.describe("Theme Choice Panel", () => { + test.beforeEach(async ({ app, user, util }) => { + // Disable the default theme for consistency in case ThemeWatcher automatically chooses it + await util.disableSystemTheme(); + await util.openAppearanceTab(); + }); + + test("should be rendered with the light theme selected", async ({ page, app, util }) => { + // Assert that 'Match system theme' is not checked + await expect(util.getMatchSystemThemeCheckbox()).not.toBeChecked(); + + // Assert that the light theme is selected + await expect(util.getLightTheme()).toBeChecked(); + // Assert that the dark and high contrast themes are not selected + await expect(util.getDarkTheme()).not.toBeChecked(); + await expect(util.getHighContrastTheme()).not.toBeChecked(); + + await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-light.png"); + }); + + test("should disable the themes when the system theme is clicked", async ({ page, app, util }) => { + await util.getMatchSystemThemeCheckbox().click(); + + // Assert that the themes are disabled + await expect(util.getLightTheme()).toBeDisabled(); + await expect(util.getDarkTheme()).toBeDisabled(); + await expect(util.getHighContrastTheme()).toBeDisabled(); + + await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-match-system-enabled.png"); + }); + + test("should change the theme to dark", async ({ page, app, util }) => { + // Assert that the light theme is selected + await expect(util.getLightTheme()).toBeChecked(); + + await util.getDarkTheme().click(); + + // Assert that the light and high contrast themes are not selected + await expect(util.getLightTheme()).not.toBeChecked(); + await expect(util.getDarkTheme()).toBeChecked(); + await expect(util.getHighContrastTheme()).not.toBeChecked(); + + await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-dark.png"); + }); + + test.describe("custom theme", () => { + test.use({ + labsFlags: ["feature_custom_themes"], + }); + + test("should render the custom theme section", async ({ page, app, util }) => { + await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme.png"); + }); + + test("should be able to add and remove a custom theme", async ({ page, app, util }) => { + await util.addCustomTheme(); + + await expect(util.getCustomTheme()).not.toBeChecked(); + await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-added.png"); + + await util.removeCustomTheme(); + await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme.png"); + }); + }); + }); +}); diff --git a/playwright/e2e/settings/general-room-settings-tab.spec.ts b/playwright/e2e/settings/general-room-settings-tab.spec.ts index ec3c14b2c..123c21428 100644 --- a/playwright/e2e/settings/general-room-settings-tab.spec.ts +++ b/playwright/e2e/settings/general-room-settings-tab.spec.ts @@ -34,7 +34,7 @@ test.describe("General room settings tab", () => { // Assert that "Show less" details element is rendered await expect(settings.getByText("Show less")).toBeVisible(); - await expect(settings).toMatchScreenshot(); + await expect(settings).toMatchScreenshot("General-room-settings-tab-should-be-rendered-properly-1.png"); // Click the "Show less" details element await settings.getByText("Show less").click(); diff --git a/playwright/e2e/settings/general-user-settings-tab.spec.ts b/playwright/e2e/settings/general-user-settings-tab.spec.ts index 41210292a..0ba85e890 100644 --- a/playwright/e2e/settings/general-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/general-user-settings-tab.spec.ts @@ -18,7 +18,6 @@ import { test, expect } from "../../element-web-test"; const USER_NAME = "Bob"; const USER_NAME_NEW = "Alice"; -const IntegrationManager = "scalar.vector.im"; test.describe("General user settings tab", () => { test.use({ @@ -73,40 +72,6 @@ test.describe("General user settings tab", () => { // Assert that the add button is rendered await expect(phoneNumbers.getByRole("button", { name: "Add" })).toBeVisible(); - // Check language and region setting dropdown - const languageInput = uut.locator(".mx_GeneralUserSettingsTab_section_languageInput"); - await languageInput.scrollIntoViewIfNeeded(); - // Check the default value - await expect(languageInput.getByText("English")).toBeVisible(); - // Click the button to display the dropdown menu - await languageInput.getByRole("button", { name: "Language Dropdown" }).click(); - // Assert that the default option is rendered and highlighted - languageInput.getByRole("option", { name: /Albanian/ }); - await expect(languageInput.getByRole("option", { name: /Albanian/ })).toHaveClass( - /mx_Dropdown_option_highlight/, - ); - await expect(languageInput.getByRole("option", { name: /Deutsch/ })).toBeVisible(); - // Click again to close the dropdown - await languageInput.getByRole("button", { name: "Language Dropdown" }).click(); - // Assert that the default value is rendered again - await expect(languageInput.getByText("English")).toBeVisible(); - - const setIdServer = uut.locator(".mx_SetIdServer"); - await setIdServer.scrollIntoViewIfNeeded(); - // Assert that an input area for identity server exists - await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible(); - - const setIntegrationManager = uut.locator(".mx_SetIntegrationManager"); - await setIntegrationManager.scrollIntoViewIfNeeded(); - await expect( - setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager", { hasText: IntegrationManager }), - ).toBeVisible(); - // Make sure integration manager's toggle switch is enabled - await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible(); - await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText( - "Manage integrations(scalar.vector.im)", - ); - // Assert the account deactivation button is displayed const accountManagementSection = uut.getByTestId("account-management-section"); await accountManagementSection.scrollIntoViewIfNeeded(); @@ -120,6 +85,12 @@ test.describe("General user settings tab", () => { await expect(uut).toMatchScreenshot("general-smallscreen.png"); }); + test("should show tooltips on narrow screen", async ({ page, uut }) => { + await page.setViewportSize({ width: 700, height: 600 }); + await page.getByRole("tab", { name: "General" }).hover(); + await expect(page.getByRole("tooltip")).toHaveText("General"); + }); + test("should support adding and removing a profile picture", async ({ uut, page }) => { const profileSettings = uut.locator(".mx_UserProfileSettings"); // Upload a picture diff --git a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts index 2dbd26716..a67909b47 100644 --- a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts @@ -1,5 +1,6 @@ /* Copyright 2023 Suguru Hirahara +Copyright 2024 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,6 +20,10 @@ import { test, expect } from "../../element-web-test"; test.describe("Preferences user settings tab", () => { test.use({ displayName: "Bob", + uut: async ({ app, user }, use) => { + const locator = await app.settings.openUserSettings("Preferences"); + await use(locator); + }, }); test("should be rendered properly", async ({ app, user }) => { @@ -26,6 +31,26 @@ test.describe("Preferences user settings tab", () => { // Assert that the top heading is rendered await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible(); - await expect(tab).toMatchScreenshot(); + await expect(tab).toMatchScreenshot("Preferences-user-settings-tab-should-be-rendered-properly-1.png"); + }); + + test("should be able to change the app language", async ({ uut, user }) => { + // Check language and region setting dropdown + const languageInput = uut.locator(".mx_GeneralUserSettingsTab_section_languageInput"); + await languageInput.scrollIntoViewIfNeeded(); + // Check the default value + await expect(languageInput.getByText("English")).toBeVisible(); + // Click the button to display the dropdown menu + await languageInput.getByRole("button", { name: "Language Dropdown" }).click(); + // Assert that the default option is rendered and highlighted + languageInput.getByRole("option", { name: /Albanian/ }); + await expect(languageInput.getByRole("option", { name: /Albanian/ })).toHaveClass( + /mx_Dropdown_option_highlight/, + ); + await expect(languageInput.getByRole("option", { name: /Deutsch/ })).toBeVisible(); + // Click again to close the dropdown + await languageInput.getByRole("button", { name: "Language Dropdown" }).click(); + // Assert that the default value is rendered again + await expect(languageInput.getByText("English")).toBeVisible(); }); }); diff --git a/playwright/e2e/settings/security-user-settings-tab.spec.ts b/playwright/e2e/settings/security-user-settings-tab.spec.ts index 08640f603..8d1f442ba 100644 --- a/playwright/e2e/settings/security-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/security-user-settings-tab.spec.ts @@ -1,5 +1,6 @@ /* Copyright 2023 Suguru Hirahara +Copyright 2024 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,6 +17,8 @@ limitations under the License. import { test, expect } from "../../element-web-test"; +const IntegrationManager = "scalar.vector.im"; + test.describe("Security user settings tab", () => { test.describe("with posthog enabled", () => { test.use({ @@ -44,8 +47,36 @@ test.describe("Security user settings tab", () => { test("should be rendered properly", async ({ app, page }) => { const tab = await app.settings.openUserSettings("Security"); await tab.getByRole("button", { name: "Learn more" }).click(); - await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(); + await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot( + "Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1.png", + ); }); }); + + test("should contain section to set ID server", async ({ app }) => { + const tab = await app.settings.openUserSettings("Security"); + + const setIdServer = tab.locator(".mx_SetIdServer"); + await setIdServer.scrollIntoViewIfNeeded(); + // Assert that an input area for identity server exists + await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible(); + }); + + test("should enable show integrations as enabled", async ({ app, page }) => { + const tab = await app.settings.openUserSettings("Security"); + + const setIntegrationManager = tab.locator(".mx_SetIntegrationManager"); + await setIntegrationManager.scrollIntoViewIfNeeded(); + await expect( + setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager", { + hasText: IntegrationManager, + }), + ).toBeVisible(); + // Make sure integration manager's toggle switch is enabled + await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible(); + await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText( + "Manage integrations(scalar.vector.im)", + ); + }); }); }); diff --git a/playwright/e2e/spaces/threads-activity-centre/index.ts b/playwright/e2e/spaces/threads-activity-centre/index.ts index 8bafe2e80..8b013c44b 100644 --- a/playwright/e2e/spaces/threads-activity-centre/index.ts +++ b/playwright/e2e/spaces/threads-activity-centre/index.ts @@ -337,12 +337,10 @@ export class Helpers { } /** - * Assert that the thread panel is focused (actually the 'close' button, specifically) + * Assert that the thread tab is focused */ - assertThreadPanelFocused() { - return expect( - this.page.locator(".mx_ThreadPanel").locator(".mx_BaseCard_header").getByLabel("Close"), - ).toBeFocused(); + assertThreadTabFocused() { + return expect(this.page.locator("#thread-panel-tab")).toBeFocused(); } /** diff --git a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts index 7d0b694ef..66a3bc58e 100644 --- a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts +++ b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts @@ -161,17 +161,12 @@ test.describe("Threads Activity Centre", () => { await util.assertNoTacIndicator(); }); - test("should focus the thread panel close button when clicking an item in the TAC", async ({ - room1, - room2, - util, - msg, - }) => { + test("should focus the thread tab when clicking an item in the TAC", async ({ room1, room2, util, msg }) => { await util.receiveMessages(room1, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); await util.openTac(); await util.clickRoomInTac(room1.name); - await util.assertThreadPanelFocused(); + await util.assertThreadTabFocused(); }); }); diff --git a/playwright/e2e/spotlight/spotlight.spec.ts b/playwright/e2e/spotlight/spotlight.spec.ts index 177eccdc1..5d10937b6 100644 --- a/playwright/e2e/spotlight/spotlight.spec.ts +++ b/playwright/e2e/spotlight/spotlight.spec.ts @@ -21,7 +21,7 @@ import type { Locator, Page } from "@playwright/test"; import type { ElementAppPage } from "../../pages/ElementAppPage"; function roomHeaderName(page: Page): Locator { - return page.locator(".mx_LegacyRoomHeader_nametext"); + return page.locator(".mx_RoomHeader_heading"); } async function startDM(app: ElementAppPage, page: Page, name: string): Promise { diff --git a/playwright/e2e/threads/threads.spec.ts b/playwright/e2e/threads/threads.spec.ts index 9b5ea4651..7898457d0 100644 --- a/playwright/e2e/threads/threads.spec.ts +++ b/playwright/e2e/threads/threads.spec.ts @@ -252,7 +252,8 @@ test.describe("Threads", () => { await expect(locator.locator(".mx_ThreadSummary_content").getByText("How are things?")).toBeAttached(); locator = page.getByRole("button", { name: "Threads" }); - await expect(locator).toHaveClass(/mx_LegacyRoomHeader_button--unread/); // User asserts thread list unread indicator + await expect(locator).toHaveAttribute("data-indicator", "default"); // User asserts thread list unread indicator + // await expect(locator).toHaveClass(/mx_LegacyRoomHeader_button--unread/); await locator.click(); // User opens thread list // User asserts thread with correct root & latest events & unread dot @@ -433,7 +434,7 @@ test.describe("Threads", () => { await textbox.press("Enter"); await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached(); // Close thread - await locator.getByRole("button", { name: "Close" }).click(); + await locator.getByTestId("base-card-close-button").click(); // Open existing thread locator = page @@ -486,7 +487,7 @@ test.describe("Threads", () => { await textbox.press("Enter"); await expect(threadPanel.locator(".mx_EventTile_last").getByText(threadMessage)).toBeVisible(); // Close thread - await threadPanel.getByRole("button", { name: "Close" }).click(); + await threadPanel.getByTestId("base-card-close-button").click(); }; await sendMessage("Hello Mr. Bot"); @@ -495,14 +496,12 @@ test.describe("Threads", () => { await createThread("Hello again Mr. Bot", "Hello again Mr. User in a thread"); // Open thread panel - await page.getByTestId("threadsButton").click(); + await page.locator(".mx_RoomHeader").getByRole("button", { name: "Threads" }).click(); const threadPanel = page.locator(".mx_ThreadPanel"); await expect( threadPanel.locator(".mx_EventTile_last").getByText("Hello again Mr. User in a thread"), ).toBeVisible(); - // Open threads list - await page.locator(".mx_BaseCard_back").click(); const rightPanel = page.locator(".mx_RightPanel"); // Check that the threads are listed await expect(rightPanel.locator(".mx_EventTile").getByText("Hello Mr. User in a thread")).toBeVisible(); diff --git a/playwright/e2e/timeline/timeline.spec.ts b/playwright/e2e/timeline/timeline.spec.ts index 60aa1e2a2..606838519 100644 --- a/playwright/e2e/timeline/timeline.spec.ts +++ b/playwright/e2e/timeline/timeline.spec.ts @@ -410,6 +410,7 @@ test.describe("Timeline", () => { { // Exclude timestamp from snapshot of mx_MainSplit mask: [page.locator(".mx_MessageTimestamp")], + hideTooltips: true, }, ); @@ -427,6 +428,7 @@ test.describe("Timeline", () => { await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-and-messages-irc-layout.png", { // Exclude timestamp from snapshot of mx_MainSplit mask: [page.locator(".mx_MessageTimestamp")], + hideTooltips: true, }); // 3. Alignment of expanded GELS and placeholder of deleted message @@ -447,6 +449,7 @@ test.describe("Timeline", () => { await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-redaction-placeholder.png", { // Exclude timestamp from snapshot of mx_MainSplit mask: [page.locator(".mx_MessageTimestamp")], + hideTooltips: true, }); // 4. Alignment of expanded GELS, placeholder of deleted message, and emote @@ -469,6 +472,7 @@ test.describe("Timeline", () => { await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-emote-irc-layout.png", { // Exclude timestamp from snapshot of mx_MainSplit mask: [page.locator(".mx_MessageTimestamp")], + hideTooltips: true, }); }); @@ -481,6 +485,7 @@ test.describe("Timeline", () => { display: none !important; } `, + hideTooltips: true, }; await sendEvent(app.client, room.roomId); @@ -568,9 +573,9 @@ test.describe("Timeline", () => { ); }); - test("should set inline start padding to a hidden event line", async ({ page, app, room, cryptoBackend }) => { + test("should set inline start padding to a hidden event line", async ({ page, app, room }) => { test.skip( - cryptoBackend === "rust", + true, "Disabled due to screenshot test being flaky - https://github.com/element-hq/element-web/issues/26890", ); await sendEvent(app.client, room.roomId); @@ -779,12 +784,12 @@ test.describe("Timeline", () => { await sendEvent(app.client, room.roomId, true); await page.goto(`/#/room/${room.roomId}`); - await page.locator(".mx_LegacyRoomHeader").getByRole("button", { name: "Search" }).click(); + await app.toggleRoomInfoPanel(); - await expect(page.locator(".mx_SearchBar")).toMatchScreenshot("search-bar-on-timeline.png"); + await page.locator(".mx_RoomSummaryCard_search").getByRole("searchbox").fill("Message"); + await page.locator(".mx_RoomSummaryCard_search").getByRole("searchbox").press("Enter"); - await page.locator(".mx_SearchBar_input").getByRole("textbox").fill("Message"); - await page.locator(".mx_SearchBar_input").getByRole("textbox").press("Enter"); + await expect(page.locator(".mx_RoomSearchAuxPanel")).toMatchScreenshot("search-aux-panel.png"); for (const locator of await page .locator(".mx_EventTile:not(.mx_EventTile_contextual) .mx_EventTile_searchHighlight") @@ -804,7 +809,7 @@ test.describe("Timeline", () => { await page.goto(`/#/room/${room.roomId}`); // Open a room setting dialog - await page.getByRole("button", { name: "Room options" }).click(); + await app.toggleRoomInfoPanel(); await page.getByRole("menuitem", { name: "Settings" }).click(); // Set a room topic to render a TextualEvent @@ -818,12 +823,9 @@ test.describe("Timeline", () => { page.getByText(`${OLD_NAME} changed the topic to "This is a room for ${stringToSearch}.".`), ).toHaveClass(/mx_TextualEvent/); - // Display the room search bar - await page.locator(".mx_LegacyRoomHeader").getByRole("button", { name: "Search" }).click(); - // Search the string to display both the message and TextualEvent on search results panel - await page.locator(".mx_SearchBar").getByRole("textbox").fill(stringToSearch); - await page.locator(".mx_SearchBar").getByRole("textbox").press("Enter"); + await page.locator(".mx_RoomSummaryCard_search").getByRole("searchbox").fill(stringToSearch); + await page.locator(".mx_RoomSummaryCard_search").getByRole("searchbox").press("Enter"); // On search results panel const resultsPanel = page.locator(".mx_RoomView_searchResultsPanel"); @@ -1199,10 +1201,10 @@ test.describe("Timeline", () => { // Install our mocks and preventative measures await context.route("**/_matrix/client/versions", async (route) => { - // Force enable MSC3916, which may require the service worker's internal cache to be cleared later. + // Force enable MSC3916/Matrix 1.11, which may require the service worker's internal cache to be cleared later. const json = await (await route.fetch()).json(); - if (!json["unstable_features"]) json["unstable_features"] = {}; - json["unstable_features"]["org.matrix.msc3916"] = true; + if (!json["versions"]) json["versions"] = []; + json["versions"].push("v1.11"); await route.fulfill({ json }); }); await context.route("**/_matrix/media/*/download/**", async (route) => { @@ -1219,14 +1221,14 @@ test.describe("Timeline", () => { json: { errcode: "M_UNKNOWN", error: "Unexpected route called." }, }); }); - await context.route("**/_matrix/client/unstable/org.matrix.msc3916/download/**", async (route) => { + await context.route("**/_matrix/client/v1/download/**", async (route) => { expect(route.request().headers()["Authorization"]).toBeDefined(); // we can't use route.continue() because no configured homeserver supports MSC3916 yet await route.fulfill({ body: NEW_AVATAR, }); }); - await context.route("**/_matrix/client/unstable/org.matrix.msc3916/thumbnail/**", async (route) => { + await context.route("**/_matrix/client/v1/thumbnail/**", async (route) => { expect(route.request().headers()["Authorization"]).toBeDefined(); // we can't use route.continue() because no configured homeserver supports MSC3916 yet await route.fulfill({ diff --git a/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts b/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts index 09a140d44..0799dc0f6 100644 --- a/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts +++ b/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts @@ -35,7 +35,9 @@ test.describe("User Onboarding (new user)", () => { }); test("page is shown and preference exists", async ({ page, app }) => { - await expect(page.locator(".mx_UserOnboardingPage")).toMatchScreenshot(); + await expect(page.locator(".mx_UserOnboardingPage")).toMatchScreenshot( + "User-Onboarding-new-user-page-is-shown-and-preference-exists-1.png", + ); await app.settings.openUserSettings("Preferences"); await expect(page.getByText("Show shortcut to welcome checklist above the room list")).toBeVisible(); }); diff --git a/playwright/e2e/utils.ts b/playwright/e2e/utils.ts index 30aff64dd..e7587c7df 100644 --- a/playwright/e2e/utils.ts +++ b/playwright/e2e/utils.ts @@ -17,8 +17,8 @@ limitations under the License. */ import { uniqueId } from "lodash"; +import { expect, type Page } from "@playwright/test"; -import type { Page } from "@playwright/test"; import type { ClientEvent, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { Client } from "../pages/client"; @@ -63,4 +63,15 @@ export async function waitForRoom( ); } +export async function selectHomeserver(page: Page, homeserverUrl: string) { + await page.getByRole("button", { name: "Edit" }).click(); + await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserverUrl); + await page.getByRole("button", { name: "Continue", exact: true }).click(); + // wait for the dialog to go away + await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0); + + await expect(page.locator(".mx_Spinner")).toHaveCount(0); + await expect(page.locator(".mx_ServerPicker_server")).toHaveText(homeserverUrl); +} + export const CommandOrControl = process.platform === "darwin" ? "Meta" : "Control"; diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts index 231797889..d6fd1b48c 100644 --- a/playwright/element-web-test.ts +++ b/playwright/element-web-test.ts @@ -15,9 +15,10 @@ limitations under the License. */ import { test as base, expect as baseExpect, Locator, Page, ExpectMatcherState, ElementHandle } from "@playwright/test"; +import { sanitizeForFilePath } from "playwright-core/lib/utils"; import AxeBuilder from "@axe-core/playwright"; import _ from "lodash"; -import { basename } from "node:path"; +import { basename, extname } from "node:path"; import type mailhog from "mailhog"; import type { IConfigOptions } from "../src/IConfigOptions"; @@ -63,84 +64,77 @@ const CONFIG_JSON: Partial = { }, }; -export type TestOptions = { - cryptoBackend: "legacy" | "rust"; -}; - interface CredentialsWithDisplayName extends Credentials { displayName: string; } -export const test = base.extend< - TestOptions & { - axe: AxeBuilder; - checkA11y: () => Promise; - - /** - * The contents of the config.json to send when the client requests it. - */ - config: typeof CONFIG_JSON; - - /** - * The options with which to run the {@link #homeserver} fixture. - */ - startHomeserverOpts: StartHomeserverOpts | string; - - homeserver: HomeserverInstance; - oAuthServer: { port: number }; - - /** - * The displayname to use for the user registered in {@link #credentials}. - * - * To set it, call `test.use({ displayName: "myDisplayName" })` in the test file or `describe` block. - * See {@link https://playwright.dev/docs/api/class-test#test-use}. - */ - displayName?: string; - - /** - * A test fixture which registers a test user on the {@link #homeserver} and supplies the details - * of the registered user. - */ - credentials: CredentialsWithDisplayName; - - /** - * The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`}, - * but adds an initScript which will populate localStorage with the user's details from - * {@link #credentials} and {@link #homeserver}. - * - * Similar to {@link #user}, but doesn't load the app. - */ - pageWithCredentials: Page; - - /** - * A (rather poorly-named) test fixture which registers a user per {@link #credentials}, stores - * the credentials into localStorage per {@link #homeserver}, and then loads the front page of the - * app. - */ - user: CredentialsWithDisplayName; - - /** - * The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`}, - * but wraps the returned `Page` in a class of utilities for interacting with the Element-Web UI, - * {@link ElementAppPage}. - */ - app: ElementAppPage; - - mailhog: { api: mailhog.API; instance: Instance }; - crypto: Crypto; - room?: { roomId: string }; - toasts: Toasts; - uut?: Locator; // Unit Under Test, useful place to refer a prepared locator - botCreateOpts: CreateBotOpts; - bot: Bot; - slidingSyncProxy: ProxyInstance; - labsFlags: string[]; - webserver: Webserver; - } ->({ - cryptoBackend: ["legacy", { option: true }], +export const test = base.extend<{ + axe: AxeBuilder; + checkA11y: () => Promise; + + /** + * The contents of the config.json to send when the client requests it. + */ + config: typeof CONFIG_JSON; + + /** + * The options with which to run the {@link #homeserver} fixture. + */ + startHomeserverOpts: StartHomeserverOpts | string; + + homeserver: HomeserverInstance; + oAuthServer: { port: number }; + + /** + * The displayname to use for the user registered in {@link #credentials}. + * + * To set it, call `test.use({ displayName: "myDisplayName" })` in the test file or `describe` block. + * See {@link https://playwright.dev/docs/api/class-test#test-use}. + */ + displayName?: string; + + /** + * A test fixture which registers a test user on the {@link #homeserver} and supplies the details + * of the registered user. + */ + credentials: CredentialsWithDisplayName; + + /** + * The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`}, + * but adds an initScript which will populate localStorage with the user's details from + * {@link #credentials} and {@link #homeserver}. + * + * Similar to {@link #user}, but doesn't load the app. + */ + pageWithCredentials: Page; + + /** + * A (rather poorly-named) test fixture which registers a user per {@link #credentials}, stores + * the credentials into localStorage per {@link #homeserver}, and then loads the front page of the + * app. + */ + user: CredentialsWithDisplayName; + + /** + * The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`}, + * but wraps the returned `Page` in a class of utilities for interacting with the Element-Web UI, + * {@link ElementAppPage}. + */ + app: ElementAppPage; + + mailhog: { api: mailhog.API; instance: Instance }; + crypto: Crypto; + room?: { roomId: string }; + toasts: Toasts; + uut?: Locator; // Unit Under Test, useful place to refer a prepared locator + botCreateOpts: CreateBotOpts; + bot: Bot; + slidingSyncProxy: ProxyInstance; + labsFlags: string[]; + webserver: Webserver; +}>({ config: CONFIG_JSON, - page: async ({ context, page, config, cryptoBackend, labsFlags }, use) => { + page: async ({ context, page, config, labsFlags }, use) => { await context.route(`http://localhost:8080/config.json*`, async (route) => { const json = { ...CONFIG_JSON, ...config }; json["features"] = { @@ -151,10 +145,6 @@ export const test = base.extend< return obj; }, {}), }; - // the default is to use rust now, so set to `false` if on legacy backend - if (cryptoBackend === "legacy") { - json.features.feature_rust_crypto = false; - } await route.fulfill({ json }); }); await use(page); @@ -309,20 +299,40 @@ export const test = base.extend< }, }); +// Based on https://github.com/microsoft/playwright/blob/2b77ed4d7aafa85a600caa0b0d101b72c8437eeb/packages/playwright/src/util.ts#L206C8-L210C2 +function sanitizeFilePathBeforeExtension(filePath: string): string { + const ext = extname(filePath); + const base = filePath.substring(0, filePath.length - ext.length); + return sanitizeForFilePath(base) + ext; +} + export const expect = baseExpect.extend({ async toMatchScreenshot( this: ExpectMatcherState, receiver: Page | Locator, - name?: `${string}.png`, + name: `${string}.png`, options?: { mask?: Array; omitBackground?: boolean; + hideTooltips?: boolean; timeout?: number; css?: string; }, ) { + const testInfo = test.info(); + if (!testInfo) throw new Error(`toMatchScreenshot() must be called during the test`); + const page = "page" in receiver ? receiver.page() : receiver; + let hideTooltipsCss: string | undefined; + if (options?.hideTooltips) { + hideTooltipsCss = ` + .mx_Tooltip_visible { + visibility: hidden !important; + } + `; + } + // We add a custom style tag before taking screenshots const style = (await page.addStyleTag({ content: ` @@ -342,17 +352,31 @@ export const expect = baseExpect.extend({ .mx_ReplyChain { border-left-color: var(--cpd-color-blue-1200) !important; } + /* Avoid flakiness from hover styling */ + .mx_ReplyChain_show { + color: var(--cpd-color-text-secondary) !important; + } /* Use monospace font for timestamp for consistent mask width */ .mx_MessageTimestamp { font-family: Inconsolata !important; } + ${hideTooltipsCss ?? ""} ${options?.css ?? ""} `, })) as ElementHandle; - await baseExpect(receiver).toHaveScreenshot(name, options); + const screenshotName = sanitizeFilePathBeforeExtension(name); + await baseExpect(receiver).toHaveScreenshot(screenshotName, options); await style.evaluate((tag) => tag.remove()); + + testInfo.annotations.push({ + // `_` prefix hides it from the HTML reporter + type: "_screenshot", + // include a path relative to `playwright/snapshots/` + description: testInfo.snapshotPath(screenshotName).split("/playwright/snapshots/", 2)[1], + }); + return { pass: true, message: () => "", name: "toMatchScreenshot" }; }, }); diff --git a/playwright/flaky-reporter.ts b/playwright/flaky-reporter.ts index 3d358bb74..95023e31b 100644 --- a/playwright/flaky-reporter.ts +++ b/playwright/flaky-reporter.ts @@ -53,7 +53,10 @@ class FlakyReporter implements Reporter { const headers = { Authorization: `Bearer ${GITHUB_TOKEN}` }; // Fetch all existing issues with the flaky-test label. - const issuesRequest = await fetch(`${GITHUB_API_URL}/repos/${REPO}/issues?labels=${LABEL}`, { headers }); + const issuesRequest = await fetch( + `${GITHUB_API_URL}/repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=100&sort=created`, + { headers }, + ); const issues = await issuesRequest.json(); for (const flake of this.flakes) { const title = ISSUE_TITLE_PREFIX + "`" + flake + "`"; @@ -61,6 +64,12 @@ class FlakyReporter implements Reporter { if (existingIssue) { console.log(`Found issue ${existingIssue.number} for ${flake}, adding comment...`); + // Ensure that the test is open + await fetch(existingIssue.url, { + method: "PATCH", + headers, + body: JSON.stringify({ state: "open" }), + }); await fetch(`${existingIssue.url}/comments`, { method: "POST", headers, diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts index ac9b4ffef..af7999435 100644 --- a/playwright/pages/ElementAppPage.ts +++ b/playwright/pages/ElementAppPage.ts @@ -171,4 +171,13 @@ export class ElementAppPage { await spotlight.open(); return spotlight; } + + /** + * Opens/closes the room info panel + * @returns locator to the right panel + */ + public async toggleRoomInfoPanel(): Promise { + await this.page.getByRole("button", { name: "Room info" }).first().click(); + return this.page.locator(".mx_RightPanel"); + } } diff --git a/playwright/pages/bot.ts b/playwright/pages/bot.ts index 333d895df..3b4613010 100644 --- a/playwright/pages/bot.ts +++ b/playwright/pages/bot.ts @@ -45,10 +45,6 @@ export interface CreateBotOpts { * Whether to generate cross-signing keys */ bootstrapCrossSigning?: boolean; - /** - * Whether to use the rust crypto impl. Defaults to false (for now!) - */ - rustCrypto?: boolean; /** * Whether to bootstrap the secret storage */ @@ -188,11 +184,7 @@ export class Bot extends Client { return cli; } - if (opts.rustCrypto) { - await cli.initRustCrypto({ useIndexedDB: false }); - } else { - await cli.initCrypto(); - } + await cli.initRustCrypto({ useIndexedDB: false }); cli.setGlobalErrorOnUnknownDevices(false); await cli.startClient(); diff --git a/playwright/pages/settings.ts b/playwright/pages/settings.ts index c0efb6770..1b7d099c6 100644 --- a/playwright/pages/settings.ts +++ b/playwright/pages/settings.ts @@ -91,12 +91,17 @@ export class Settings { } /** - * Open room settings (via room header menu), returns a locator to the dialog + * Open room settings (via room info panel), returns a locator to the dialog * @param tab the name of the tab to switch to after opening, optional. */ public async openRoomSettings(tab?: string): Promise { - await this.page.getByRole("banner").getByRole("button", { name: "Room options", exact: true }).click(); - await this.page.locator(".mx_RoomTile_contextMenu").getByRole("menuitem", { name: "Settings" }).click(); + // Open right panel if not open + const rightPanel = this.page.locator(".mx_RightPanel"); + if ((await rightPanel.count()) === 0) { + await this.page.getByRole("button", { name: "Room info" }).first().click(); + } + await rightPanel.getByRole("menuitem", { name: "Settings" }).click(); + if (tab) await this.switchTab(tab); return this.page.locator(".mx_Dialog").filter({ has: this.page.locator(".mx_RoomSettingsDialog") }); } diff --git a/playwright/plugins/homeserver/index.ts b/playwright/plugins/homeserver/index.ts index 1e0cfb3b3..b14ba7008 100644 --- a/playwright/plugins/homeserver/index.ts +++ b/playwright/plugins/homeserver/index.ts @@ -39,6 +39,15 @@ export interface HomeserverInstance { * @param password login password */ loginUser(userId: string, password: string): Promise; + + /** + * Sets a third party identifier for the given user. This only supports setting a single 3pid and will + * replace any others. + * @param userId The full ID of the user to edit (as returned from registerUser) + * @param medium The medium of the 3pid to set + * @param address The address of the 3pid to set + */ + setThreepid(userId: string, medium: string, address: string): Promise; } export interface StartHomeserverOpts { diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index c88fd641d..5cf639172 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for `matrixdotorg/synapse` image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:c357ea1486c8cd2613932e97bd2f6ff4e8b4c4fafcb2c28d1e8ec0d383c56d9d"; +const DOCKER_TAG = "develop@sha256:9e193236098ae5ff66c9bf79252e318fd561ceb1322d5495780a11d9dbdcfb17"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); @@ -94,6 +94,8 @@ export class Synapse implements Homeserver, HomeserverInstance { protected docker: Docker = new Docker(); public config: HomeserverConfig & { serverId: string }; + private adminToken?: string; + public constructor(private readonly request: APIRequestContext) {} /** @@ -152,12 +154,17 @@ export class Synapse implements Homeserver, HomeserverInstance { return [path.join(synapseLogsPath, "stdout.log"), path.join(synapseLogsPath, "stderr.log")]; } - public async registerUser(username: string, password: string, displayName?: string): Promise { + private async registerUserInternal( + username: string, + password: string, + displayName?: string, + admin = false, + ): Promise { const url = `${this.config.baseUrl}/_synapse/admin/v1/register`; const { nonce } = await this.request.get(url).then((r) => r.json()); const mac = crypto .createHmac("sha1", this.config.registrationSecret) - .update(`${nonce}\0${username}\0${password}\0notadmin`) + .update(`${nonce}\0${username}\0${password}\0${admin ? "" : "not"}admin`) .digest("hex"); const res = await this.request.post(url, { data: { @@ -165,7 +172,7 @@ export class Synapse implements Homeserver, HomeserverInstance { username, password, mac, - admin: false, + admin, displayname: displayName, }, }); @@ -185,6 +192,10 @@ export class Synapse implements Homeserver, HomeserverInstance { }; } + public registerUser(username: string, password: string, displayName?: string): Promise { + return this.registerUserInternal(username, password, displayName, false); + } + public async loginUser(userId: string, password: string): Promise { const url = `${this.config.baseUrl}/_matrix/client/v3/login`; const res = await this.request.post(url, { @@ -207,4 +218,30 @@ export class Synapse implements Homeserver, HomeserverInstance { homeServer: json.home_server, }; } + + public async setThreepid(userId: string, medium: string, address: string): Promise { + if (this.adminToken === undefined) { + const result = await this.registerUserInternal("admin", "totalyinsecureadminpassword", undefined, true); + this.adminToken = result.accessToken; + } + + const url = `${this.config.baseUrl}/_synapse/admin/v2/users/${userId}`; + const res = await this.request.put(url, { + data: { + threepids: [ + { + medium, + address, + }, + ], + }, + headers: { + Authorization: `Bearer ${this.adminToken}`, + }, + }); + + if (!res.ok()) { + throw await res.json(); + } + } } diff --git a/playwright/plugins/homeserver/synapse/templates/guest-enabled/README.md b/playwright/plugins/homeserver/synapse/templates/guest-enabled/README.md new file mode 100644 index 000000000..e1fef0b9d --- /dev/null +++ b/playwright/plugins/homeserver/synapse/templates/guest-enabled/README.md @@ -0,0 +1 @@ +A synapse configured with guest registration enabled. diff --git a/playwright/plugins/homeserver/synapse/templates/guest-enabled/homeserver.yaml b/playwright/plugins/homeserver/synapse/templates/guest-enabled/homeserver.yaml new file mode 100644 index 000000000..1faa39c3a --- /dev/null +++ b/playwright/plugins/homeserver/synapse/templates/guest-enabled/homeserver.yaml @@ -0,0 +1,105 @@ +server_name: "localhost" +pid_file: /data/homeserver.pid +public_baseurl: "{{PUBLIC_BASEURL}}" +listeners: + - port: 8008 + tls: false + bind_addresses: ["::"] + type: http + x_forwarded: true + + resources: + - names: [client] + compress: false + +database: + name: "sqlite3" + args: + database: ":memory:" + +log_config: "/data/log.config" + +rc_messages_per_second: 10000 +rc_message_burst_count: 10000 +rc_registration: + per_second: 10000 + burst_count: 10000 +rc_joins: + local: + per_second: 9999 + burst_count: 9999 + remote: + per_second: 9999 + burst_count: 9999 +rc_joins_per_room: + per_second: 9999 + burst_count: 9999 +rc_3pid_validation: + per_second: 1000 + burst_count: 1000 + +rc_invites: + per_room: + per_second: 1000 + burst_count: 1000 + per_user: + per_second: 1000 + burst_count: 1000 + +rc_login: + address: + per_second: 10000 + burst_count: 10000 + account: + per_second: 10000 + burst_count: 10000 + failed_attempts: + per_second: 10000 + burst_count: 10000 + +media_store_path: "/data/media_store" +uploads_path: "/data/uploads" +allow_guest_access: true +enable_registration: true +enable_registration_without_verification: true +disable_msisdn_registration: false +registration_shared_secret: "{{REGISTRATION_SECRET}}" +report_stats: false +macaroon_secret_key: "{{MACAROON_SECRET_KEY}}" +form_secret: "{{FORM_SECRET}}" +signing_key_path: "/data/localhost.signing.key" + +trusted_key_servers: + - server_name: "matrix.org" +suppress_key_server_warning: true + +ui_auth: + session_timeout: "300s" + +oidc_providers: + - idp_id: test + idp_name: "OAuth test" + issuer: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth" + authorization_endpoint: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth/auth.html" + # the token endpoint receives requests from synapse, rather than the webapp, so needs to escape the docker container. + token_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/token" + userinfo_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/userinfo" + client_id: "synapse" + discover: false + scopes: ["profile"] + skip_verification: true + client_auth_method: none + user_mapping_provider: + config: + display_name_template: "{{ user.name }}" + +# Inhibit background updates as this Synapse isn't long-lived +background_updates: + min_batch_size: 100000 + sleep_duration_ms: 100000 + +experimental_features: + # Needed for e2e/crypto/crypto.spec.ts > Cryptography > decryption failure + # messages > non-joined historical messages. + # Can be removed after Synapse enables it by default + msc4115_membership_on_events: true diff --git a/playwright/plugins/homeserver/synapse/templates/guest-enabled/log.config b/playwright/plugins/homeserver/synapse/templates/guest-enabled/log.config new file mode 100644 index 000000000..ac232762d --- /dev/null +++ b/playwright/plugins/homeserver/synapse/templates/guest-enabled/log.config @@ -0,0 +1,50 @@ +# Log configuration for Synapse. +# +# This is a YAML file containing a standard Python logging configuration +# dictionary. See [1] for details on the valid settings. +# +# Synapse also supports structured logging for machine readable logs which can +# be ingested by ELK stacks. See [2] for details. +# +# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema +# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html + +version: 1 + +formatters: + precise: + format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s' + +handlers: + # A handler that writes logs to stderr. Unused by default, but can be used + # instead of "buffer" and "file" in the logger handlers. + console: + class: logging.StreamHandler + formatter: precise + +loggers: + synapse.storage.SQL: + # beware: increasing this to DEBUG will make synapse log sensitive + # information such as access tokens. + level: INFO + + twisted: + # We send the twisted logging directly to the file handler, + # to work around https://github.com/matrix-org/synapse/issues/3471 + # when using "buffer" logger. Use "console" to log to stderr instead. + handlers: [console] + propagate: false + +root: + level: INFO + + # Write logs to the `buffer` handler, which will buffer them together in memory, + # then write them to a file. + # + # Replace "buffer" with "console" to log to stderr instead. (Note that you'll + # also need to update the configuration for the `twisted` logger above, in + # this case.) + # + handlers: [console] + +disable_existing_loggers: false diff --git a/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-CompatibilityView-linux.png b/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-CompatibilityView-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..df1a44f523cab9eefc55931f421e7e9c02c08d4d GIT binary patch literal 70539 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 H*{%Nv>STUi literal 0 HcmV?d00001 diff --git a/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-linux.png b/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..96ad96e3e119ecc46b2d7a2f2b198aeacf1178de GIT binary patch literal 59582 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 VMm}bcx)GJlRjF%|SrU5p{tqXm!j1p{ literal 0 HcmV?d00001 diff --git a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..ae11ec9eeca054bd867faff22735e6a80f57c85e GIT binary patch literal 40619 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

Vo-^Wv0a^k?RRh8DoP*ZY)>QOyFh}vn6y-h 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

-
with an existing session onAction() room actions leave_r
+
`; @@ -390,12 +390,6 @@ exports[` with an existing session onAction() room actions leave_r Leave space
-
with an existing session onAction() room actions leave_r
+
`; diff --git a/test/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/components/structures/__snapshots__/RoomView-test.tsx.snap index 513476990..2419d5f40 100644 --- a/test/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -6,45 +6,142 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1 class="mx_RoomView mx_RoomView--local" >
+
+ + + + +
+
+ + u + u
+ 2
-
-
- @user:example.com -
-
-
+ +
+ + + + +
+ + u + u
+ 2
-
-
- @user:example.com -
-
-
+
+ + + + +
+
+ + u + u
+ 2
-
-
- @user:example.com -
-
-
-
-

- Threads -

`; - -exports[`ThreadPanel Header matches snapshot when no threads 1`] = ` - -
-

- Threads -

-
-
-`; diff --git a/test/components/views/context_menus/RoomGeneralContextMenu-test.tsx b/test/components/views/context_menus/RoomGeneralContextMenu-test.tsx index 82255b764..c64e0b2e6 100644 --- a/test/components/views/context_menus/RoomGeneralContextMenu-test.tsx +++ b/test/components/views/context_menus/RoomGeneralContextMenu-test.tsx @@ -20,6 +20,7 @@ import { ReceiptType, MatrixClient, PendingEventOrdering, Room } from "matrix-js import { KnownMembership } from "matrix-js-sdk/src/types"; import React from "react"; import userEvent from "@testing-library/user-event"; +import { sleep } from "matrix-js-sdk/src/utils"; import { ChevronFace } from "../../../../src/components/structures/ContextMenu"; import { @@ -35,7 +36,7 @@ import { mkMessage, stubClient } from "../../../test-utils/test-utils"; import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents"; import { UIComponent } from "../../../../src/settings/UIFeature"; import SettingsStore from "../../../../src/settings/SettingsStore"; -import Modal from "../../../../src/Modal"; +import { clearAllModals } from "../../../test-utils"; jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({ shouldShowComponent: jest.fn(), @@ -89,8 +90,8 @@ describe("RoomGeneralContextMenu", () => { onFinished = jest.fn(); }); - afterEach(() => { - Modal.closeCurrentModal("force"); + afterEach(async () => { + await clearAllModals(); }); it("renders an empty context menu for archived rooms", async () => { @@ -141,7 +142,7 @@ describe("RoomGeneralContextMenu", () => { const markAsReadBtn = getByLabelText(container, "Mark as read"); fireEvent.click(markAsReadBtn); - await new Promise(setImmediate); + await sleep(0); expect(mockClient.sendReadReceipt).toHaveBeenCalledWith(event, ReceiptType.Read, true); expect(onFinished).toHaveBeenCalled(); @@ -155,7 +156,7 @@ describe("RoomGeneralContextMenu", () => { const markAsUnreadBtn = getByLabelText(container, "Mark as unread"); fireEvent.click(markAsUnreadBtn); - await new Promise(setImmediate); + await sleep(0); expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(ROOM_ID, "com.famedly.marked_unread", { unread: true, diff --git a/test/components/views/dialogs/ForwardDialog-test.tsx b/test/components/views/dialogs/ForwardDialog-test.tsx index 12c6048e6..c28d19ae6 100644 --- a/test/components/views/dialogs/ForwardDialog-test.tsx +++ b/test/components/views/dialogs/ForwardDialog-test.tsx @@ -26,6 +26,7 @@ import { } from "matrix-js-sdk/src/matrix"; import { act, fireEvent, getByTestId, render, RenderResult, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { sleep } from "matrix-js-sdk/src/utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import ForwardDialog from "../../../../src/components/views/dialogs/ForwardDialog"; @@ -199,7 +200,7 @@ describe("ForwardDialog", () => { await act(async () => { cancelSend(); // Wait one tick for the button to realize the send failed - await new Promise((resolve) => setImmediate(resolve)); + await sleep(0); }); update(); expect(firstButton.className).toContain("mx_ForwardList_sendFailed"); @@ -215,7 +216,7 @@ describe("ForwardDialog", () => { await act(async () => { finishSend(); // Wait one tick for the button to realize the send succeeded - await new Promise((resolve) => setImmediate(resolve)); + await sleep(0); }); update(); expect(secondButton.className).toContain("mx_ForwardList_sent"); diff --git a/test/components/views/dialogs/InviteDialog-test.tsx b/test/components/views/dialogs/InviteDialog-test.tsx index 16f756cb0..4e1dca419 100644 --- a/test/components/views/dialogs/InviteDialog-test.tsx +++ b/test/components/views/dialogs/InviteDialog-test.tsx @@ -25,6 +25,7 @@ import { mocked, Mocked } from "jest-mock"; import InviteDialog from "../../../../src/components/views/dialogs/InviteDialog"; import { InviteKind } from "../../../../src/components/views/dialogs/InviteDialogTypes"; import { + clearAllModals, filterConsole, flushPromises, getMockClientWithEventEmitter, @@ -40,7 +41,6 @@ import { SdkContextClass } from "../../../../src/contexts/SDKContext"; import { IProfileInfo } from "../../../../src/hooks/useProfileInfo"; import { DirectoryMember, startDmOnFirstMessage } from "../../../../src/utils/direct-messages"; import SettingsStore from "../../../../src/settings/SettingsStore"; -import Modal from "../../../../src/Modal"; const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken"); jest.mock("../../../../src/IdentityAuthClient", () => @@ -178,8 +178,8 @@ describe("InviteDialog", () => { SdkContextClass.instance.client = mockClient; }); - afterEach(() => { - Modal.closeCurrentModal(); + afterEach(async () => { + await clearAllModals(); SdkContextClass.instance.onLoggedOut(); SdkContextClass.instance.client = undefined; }); diff --git a/test/components/views/dialogs/UserSettingsDialog-test.tsx b/test/components/views/dialogs/UserSettingsDialog-test.tsx index 72232d5e1..f404b7f20 100644 --- a/test/components/views/dialogs/UserSettingsDialog-test.tsx +++ b/test/components/views/dialogs/UserSettingsDialog-test.tsx @@ -54,6 +54,7 @@ jest.mock("../../../../src/settings/SettingsStore", () => ({ getDescription: jest.fn(), shouldHaveWarning: jest.fn(), disabledMessage: jest.fn(), + settingIsOveriddenAtConfigLevel: jest.fn(), })); jest.mock("../../../../src/SdkConfig", () => ({ diff --git a/test/components/views/dialogs/__snapshots__/AppDownloadDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/AppDownloadDialog-test.tsx.snap index 8bf4c582a..6d4e827c4 100644 --- a/test/components/views/dialogs/__snapshots__/AppDownloadDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/AppDownloadDialog-test.tsx.snap @@ -23,12 +23,6 @@ exports[`AppDownloadDialog should allow disabling desktop build 1`] = ` Download Element
-
@@ -139,6 +133,12 @@ exports[`AppDownloadDialog should allow disabling desktop build 1`] = ` Google Play and the Google Play logo are trademarks of Google LLC.

+
-
@@ -295,6 +289,12 @@ exports[`AppDownloadDialog should allow disabling fdroid build 1`] = ` Google Play and the Google Play logo are trademarks of Google LLC.

+
-
@@ -364,6 +358,12 @@ exports[`AppDownloadDialog should allow disabling mobile builds 1`] = ` Google Play and the Google Play logo are trademarks of Google LLC.

+
-
@@ -530,6 +524,12 @@ exports[`AppDownloadDialog should render with desktop, ios, android, fdroid butt Google Play and the Google Play logo are trademarks of Google LLC.

+
should fetch github proxy url for each repo with ol Changelog
-
should fetch github proxy url for each repo with ol
+
-
+
-
@@ -39,6 +33,7 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = ` > Room ID: !id
+
renders export dialog 1`] = ` Export Chat
-

Select from the options below to export chats from your timeline

@@ -200,6 +194,12 @@ exports[` renders export dialog 1`] = `
+
`; diff --git a/test/components/views/dialogs/__snapshots__/LogoutDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/LogoutDialog-test.tsx.snap index 3219b1fa7..659439751 100644 --- a/test/components/views/dialogs/__snapshots__/LogoutDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/LogoutDialog-test.tsx.snap @@ -24,12 +24,6 @@ exports[`LogoutDialog Prompts user to connect backup if there is a backup on the You'll lose access to your encrypted messages
-
+
-
+
-
+
should list spaces which are not par Select spaces
-

Decide which spaces can access this room. If a space is selected, its members can find and join @@ -141,6 +135,12 @@ exports[` should list spaces which are not par

+
should render empty state 1`] = ` Select spaces
-

Decide which spaces can access this room. If a space is selected, its members can find and join @@ -242,6 +236,12 @@ exports[` should render empty state 1`] = `

+
-
+
-
+
should match the snapshot 1`] = ` Message edits
-
should match the snapshot 1`] = `
+
should support events with 1`] = ` Message edits
-
should support events with 1`] = `
+
should render dialog 1`] = ` Sign into your homeserver
-
should render dialog 1`] = ` />
+
-

@@ -158,6 +152,12 @@ exports[`CreateKeyBackupDialog should display the success dialog when the key ba

+
-
+
-
+
", () => { + describe("with desktop builds available", () => { + beforeEach(() => { + SdkConfig.put({ + brand: "Element", + desktop_builds: { + available: true, + logo: "https://logo", + url: "https://url", + }, + }); + }); + + it("renders with a logo by default", () => { + const { asFragment, queryByRole } = render( + , + ); + expect(queryByRole("img")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("renders without a logo when showLogo=false", () => { + const { asFragment, queryByRole } = render( + , + ); + + expect(queryByRole("img")).not.toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + }); +}); diff --git a/test/components/views/elements/SyntaxHighlight-test.tsx b/test/components/views/elements/SyntaxHighlight-test.tsx index 2f8c751fd..3c59c6df4 100644 --- a/test/components/views/elements/SyntaxHighlight-test.tsx +++ b/test/components/views/elements/SyntaxHighlight-test.tsx @@ -35,6 +35,6 @@ describe("", () => { await waitFor(() => expect(container.querySelector(`.language-${lang}`)).toBeTruthy()); const [_lang, opts] = mock.mock.lastCall!; - expect((opts as HighlightOptions)["language"]).toBe(lang); + expect((opts as unknown as HighlightOptions)["language"]).toBe(lang); }); }); diff --git a/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap b/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap index b344e3cd5..05094a2ca 100644 --- a/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap +++ b/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap @@ -13,33 +13,46 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] = class="mx_BaseCard_header" >
+

+ Example 1 +

+ +
with desktop builds available renders with a logo by default 1`] = ` + +
+ + + + Use the + + Desktop app + + to search encrypted messages + + +
+
+`; + +exports[` with desktop builds available renders without a logo when showLogo=false 1`] = ` + +
+ + + Use the + + Desktop app + + to search encrypted messages + + +
+
+`; diff --git a/test/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap b/test/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap index 2886a826c..ffc676e30 100644 --- a/test/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap +++ b/test/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap @@ -23,6 +23,7 @@ exports[` renders map correctly 1`] = ` class="mx_ZoomButtons" >
{ expect(container).toMatchSnapshot(); }); - it(`Should display "The sender has blocked you from receiving this message"`, () => { + it(`Should display "The sender has blocked you from receiving this message"`, async () => { // When - const event = mkEvent({ - type: "m.room.message", - room: "myfakeroom", - user: "myfakeuser", - content: { - msgtype: "m.bad.encrypted", - }, - event: true, + const event = await mkDecryptionFailureMatrixEvent({ + code: DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE, + msg: "withheld", + roomId: "myfakeroom", + sender: "myfakeuser", }); - jest.spyOn(event, "isEncryptedDisabledForUnverifiedDevices", "get").mockReturnValue(true); + const { container } = customRender(event); // Then diff --git a/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap b/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap index c0096b646..22e44fd16 100644 --- a/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap +++ b/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap @@ -5,7 +5,7 @@ exports[`DecryptionFailureBody Should display "The sender has blocked you from r
- The sender has blocked you from receiving this message + The sender has blocked you from receiving this message because your device is unverified
`; diff --git a/test/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/components/views/right_panel/PinnedMessagesCard-test.tsx index aa49c1a55..d773b51fb 100644 --- a/test/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/test/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -32,6 +32,7 @@ import { import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent"; import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent"; +import { sleep } from "matrix-js-sdk/src/utils"; import { stubClient, mkEvent, mkMessage, flushPromises } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; @@ -98,7 +99,7 @@ describe("", () => { , ); // Wait a tick for state updates - await new Promise((resolve) => setImmediate(resolve)); + await sleep(0); }); return pins; @@ -114,7 +115,7 @@ describe("", () => { // @ts-ignore what is going on here? pinListener(room.currentState.getStateEvents()); // Wait a tick for state updates - await new Promise((resolve) => setImmediate(resolve)); + await sleep(0); }); }; diff --git a/test/components/views/right_panel/RightPanelTabs-test.tsx b/test/components/views/right_panel/RightPanelTabs-test.tsx new file mode 100644 index 000000000..dae7b1a79 --- /dev/null +++ b/test/components/views/right_panel/RightPanelTabs-test.tsx @@ -0,0 +1,72 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 React from "react"; +import { render, fireEvent } from "@testing-library/react"; + +import dis from "../../../../src/dispatcher/dispatcher"; +import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases"; +import { RightPanelTabs } from "../../../../src/components/views/right_panel/RightPanelTabs"; +import { Action } from "../../../../src/dispatcher/actions"; + +describe("", () => { + it("Component renders the correct tabs", () => { + const { container, getByRole } = render(); + expect(container).toMatchSnapshot(); + + // We expect Info, People and Threads as tabs + expect(getByRole("tab", { name: "Info" })).toBeDefined(); + expect(getByRole("tab", { name: "People" })).toBeDefined(); + expect(getByRole("tab", { name: "Threads" })).toBeDefined(); + }); + + it("Correct tab is active", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + // Assert that the active tab is Info + expect(container.querySelectorAll("[aria-selected='true'").length).toEqual(1); + expect(container.querySelector("[aria-selected='true'")).toHaveAccessibleName("People"); + }); + + it("Renders nothing for some phases, eg: FilePanel", () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("onClick behaviors work as expected", () => { + const spy = jest.spyOn(RightPanelStore.instance, "pushCard"); + const { getByRole } = render(); + + // Info -> People + fireEvent.click(getByRole("tab", { name: "People" })); + expect(spy).toHaveBeenLastCalledWith({ phase: RightPanelPhases.RoomMemberList }, true); + + // People -> Threads + fireEvent.click(getByRole("tab", { name: "Threads" })); + expect(spy).toHaveBeenLastCalledWith({ phase: RightPanelPhases.ThreadPanel }, true); + + // Threads -> Info + fireEvent.click(getByRole("tab", { name: "Info" })); + expect(spy).toHaveBeenLastCalledWith({ phase: RightPanelPhases.RoomSummary }, true); + }); + + it("Threads tab is focused on action", () => { + const { getByRole } = render(); + dis.dispatch({ action: Action.FocusThreadsPanel }, true); + expect(getByRole("tab", { name: "Threads" })).toHaveFocus(); + }); +}); diff --git a/test/components/views/right_panel/RoomSummaryCard-test.tsx b/test/components/views/right_panel/RoomSummaryCard-test.tsx index b4288dc35..1ddea7638 100644 --- a/test/components/views/right_panel/RoomSummaryCard-test.tsx +++ b/test/components/views/right_panel/RoomSummaryCard-test.tsx @@ -15,10 +15,11 @@ limitations under the License. */ import React from "react"; -import { render, fireEvent, screen } from "@testing-library/react"; +import { render, fireEvent, screen, waitFor } from "@testing-library/react"; import { EventType, MatrixEvent, Room, MatrixClient, JoinRule } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { mocked, MockedObject } from "jest-mock"; +import userEvent from "@testing-library/user-event"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; import RoomSummaryCard from "../../../../src/components/views/right_panel/RoomSummaryCard"; @@ -34,9 +35,10 @@ import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } f import { PollHistoryDialog } from "../../../../src/components/views/dialogs/PollHistoryDialog"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; import { _t } from "../../../../src/languageHandler"; -import SettingsStore from "../../../../src/settings/SettingsStore"; import { tagRoom } from "../../../../src/utils/room/tagRoom"; import { DefaultTagID } from "../../../../src/stores/room-list/models"; +import { Action } from "../../../../src/dispatcher/actions"; +import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext"; jest.mock("../../../../src/utils/room/tagRoom"); @@ -141,15 +143,80 @@ describe("", () => { expect(container).toMatchSnapshot(); }); - it("opens the search", async () => { - const onSearchClick = jest.fn(); - const { getByLabelText } = getComponent({ - onSearchClick, + describe("search", () => { + it("has the search field", async () => { + const onSearchChange = jest.fn(); + const { getByPlaceholderText } = getComponent({ + onSearchChange, + }); + expect(getByPlaceholderText("Search messages…")).toBeVisible(); + }); + + it("should focus the search field if Action.FocusMessageSearch is fired", async () => { + const onSearchChange = jest.fn(); + const { getByPlaceholderText } = getComponent({ + onSearchChange, + }); + expect(getByPlaceholderText("Search messages…")).not.toHaveFocus(); + defaultDispatcher.fire(Action.FocusMessageSearch); + await waitFor(() => { + expect(getByPlaceholderText("Search messages…")).toHaveFocus(); + }); + }); + + it("should focus the search field if focusRoomSearch=true", () => { + const onSearchChange = jest.fn(); + const { getByPlaceholderText } = getComponent({ + onSearchChange, + focusRoomSearch: true, + }); + expect(getByPlaceholderText("Search messages…")).toHaveFocus(); + }); + + it("should cancel search on escape", () => { + const onSearchChange = jest.fn(); + const onSearchCancel = jest.fn(); + const { getByPlaceholderText } = getComponent({ + onSearchChange, + onSearchCancel, + focusRoomSearch: true, + }); + expect(getByPlaceholderText("Search messages…")).toHaveFocus(); + fireEvent.keyDown(getByPlaceholderText("Search messages…"), { key: "Escape" }); + expect(onSearchCancel).toHaveBeenCalled(); }); - const searchBtn = getByLabelText(_t("action|search")); - fireEvent.click(searchBtn); - expect(onSearchClick).toHaveBeenCalled(); + it("should empty search field when the timeline rendering type changes away", async () => { + const onSearchChange = jest.fn(); + const { rerender } = render( + + + + + , + ); + + await userEvent.type(screen.getByPlaceholderText("Search messages…"), "test"); + expect(screen.getByPlaceholderText("Search messages…")).toHaveValue("test"); + + rerender( + + + + + , + ); + expect(screen.getByPlaceholderText("Search messages…")).toHaveValue(""); + }); }); it("opens room file panel on button click", () => { @@ -200,18 +267,6 @@ describe("", () => { expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "open_room_settings" }); }); - it("renders room members options when new room UI is not enabled", () => { - jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("common|people"))); - - expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith( - { phase: RightPanelPhases.RoomMemberList }, - true, - ); - }); - describe("pinning", () => { it("renders pins options when pinning feature is enabled", () => { mocked(settingsHooks.useFeatureEnabled).mockImplementation((feature) => feature === "feature_pinning"); diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx index bc314e9e3..3b3646878 100644 --- a/test/components/views/right_panel/UserInfo-test.tsx +++ b/test/components/views/right_panel/UserInfo-test.tsx @@ -56,6 +56,9 @@ import { clearAllModals, flushPromises } from "../../../test-utils"; import ErrorDialog from "../../../../src/components/views/dialogs/ErrorDialog"; import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents"; import { UIComponent } from "../../../../src/settings/UIFeature"; +import { Action } from "../../../../src/dispatcher/actions"; +import ShareDialog from "../../../../src/components/views/dialogs/ShareDialog"; +import BulkRedactDialog from "../../../../src/components/views/dialogs/BulkRedactDialog"; jest.mock("../../../../src/utils/direct-messages", () => ({ ...jest.requireActual("../../../../src/utils/direct-messages"), @@ -302,15 +305,6 @@ describe("", () => { expect(screen.queryByTestId("space-header")).not.toBeInTheDocument(); }); - it("renders space header when room is a space room", () => { - const spaceRoom = { - ...mockRoom, - isSpaceRoom: jest.fn().mockReturnValue(true), - }; - renderComponent({ room: spaceRoom }); - expect(screen.getByTestId("space-header")).toBeInTheDocument(); - }); - it("renders encryption info panel without pending verification", () => { renderComponent({ phase: RightPanelPhases.EncryptionPanel, room: mockRoom }); expect(screen.getByRole("heading", { name: /encryption/i })).toBeInTheDocument(); @@ -332,7 +326,7 @@ describe("", () => { , ); - screen.getByRole("button", { name: "Message" }); + screen.getByRole("button", { name: "Send message" }); }); it("hides the message button if the visibility customisation hides all create room features", () => { @@ -351,6 +345,64 @@ describe("", () => { }, ); }); + + describe("Ignore", () => { + const member = new RoomMember(defaultRoomId, defaultUserId); + + it("shows block button when member userId does not match client userId", () => { + // call to client.getUserId returns undefined, which will not match member.userId + renderComponent(); + + expect(screen.getByRole("button", { name: "Ignore" })).toBeInTheDocument(); + }); + + it("shows a modal before ignoring the user", async () => { + const originalCreateDialog = Modal.createDialog; + const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({ + finished: Promise.resolve([true]), + close: () => {}, + })); + + try { + mockClient.getIgnoredUsers.mockReturnValue([]); + renderComponent(); + + await userEvent.click(screen.getByRole("button", { name: "Ignore" })); + expect(modalSpy).toHaveBeenCalled(); + expect(mockClient.setIgnoredUsers).toHaveBeenLastCalledWith([member.userId]); + } finally { + Modal.createDialog = originalCreateDialog; + } + }); + + it("cancels ignoring the user", async () => { + const originalCreateDialog = Modal.createDialog; + const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({ + finished: Promise.resolve([false]), + close: () => {}, + })); + + try { + mockClient.getIgnoredUsers.mockReturnValue([]); + renderComponent(); + + await userEvent.click(screen.getByRole("button", { name: "Ignore" })); + expect(modalSpy).toHaveBeenCalled(); + expect(mockClient.setIgnoredUsers).not.toHaveBeenCalled(); + } finally { + Modal.createDialog = originalCreateDialog; + } + }); + + it("unignores the user", async () => { + mockClient.isUserIgnored.mockReturnValue(true); + mockClient.getIgnoredUsers.mockReturnValue([member.userId]); + renderComponent(); + + await userEvent.click(screen.getByRole("button", { name: "Unignore" })); + expect(mockClient.setIgnoredUsers).toHaveBeenCalledWith([]); + }); + }); }); describe("with crypto enabled", () => { @@ -810,7 +862,7 @@ describe("", () => { describe("", () => { const member = new RoomMember(defaultRoomId, defaultUserId); - const defaultProps = { member, isIgnored: false, canInvite: false, isSpace: false }; + const defaultProps = { member, canInvite: false, isSpace: false }; const renderComponent = (props = {}) => { const Wrapper = (wrapperProps = {}) => { @@ -837,9 +889,13 @@ describe("", () => { inviteSpy.mockRestore(); }); - it("always shows share user button", () => { + it("always shows share user button and clicking it should produce a ShareDialog", async () => { + const spy = jest.spyOn(Modal, "createDialog"); + renderComponent(); - expect(screen.getByRole("button", { name: /share link to user/i })).toBeInTheDocument(); + await userEvent.click(screen.getByRole("button", { name: "Share profile" })); + + expect(spy).toHaveBeenCalledWith(ShareDialog, { target: defaultProps.member }); }); it("does not show ignore or direct message buttons when member userId matches client userId", () => { @@ -851,20 +907,31 @@ describe("", () => { expect(screen.queryByRole("button", { name: /message/i })).not.toBeInTheDocument(); }); - it("shows ignore, direct message and mention buttons when member userId does not match client userId", () => { + it("shows direct message and mention buttons when member userId does not match client userId", () => { // call to client.getUserId returns undefined, which will not match member.userId renderComponent(); - expect(screen.getByRole("button", { name: /ignore/i })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /message/i })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /mention/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Send message" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Mention" })).toBeInTheDocument(); + }); + + it("mention button fires ComposerInsert Action", async () => { + renderComponent(); + + const button = screen.getByRole("button", { name: "Mention" }); + await userEvent.click(button); + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.ComposerInsert, + timelineRenderingType: "Room", + userId: "@user:example.com", + }); }); it("when call to client.getRoom is null, does not show read receipt button", () => { mockClient.getRoom.mockReturnValueOnce(null); renderComponent(); - expect(screen.queryByRole("button", { name: /jump to read receipt/i })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Jump to read receipt" })).not.toBeInTheDocument(); }); it("when call to client.getRoom is non-null and room.getEventReadUpTo is null, does not show read receipt button", () => { @@ -872,7 +939,7 @@ describe("", () => { mockClient.getRoom.mockReturnValueOnce(mockRoom); renderComponent(); - expect(screen.queryByRole("button", { name: /jump to read receipt/i })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Jump to read receipt" })).not.toBeInTheDocument(); }); it("when calls to client.getRoom and room.getEventReadUpTo are non-null, shows read receipt button", () => { @@ -880,7 +947,7 @@ describe("", () => { mockClient.getRoom.mockReturnValueOnce(mockRoom); renderComponent(); - expect(screen.getByRole("button", { name: /jump to read receipt/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Jump to read receipt" })).toBeInTheDocument(); }); it("clicking the read receipt button calls dispatch with correct event_id", async () => { @@ -889,7 +956,7 @@ describe("", () => { mockClient.getRoom.mockReturnValue(mockRoom); renderComponent(); - const readReceiptButton = screen.getByRole("button", { name: /jump to read receipt/i }); + const readReceiptButton = screen.getByRole("button", { name: "Jump to read receipt" }); expect(readReceiptButton).toBeInTheDocument(); await userEvent.click(readReceiptButton); @@ -913,7 +980,7 @@ describe("", () => { mockClient.getRoom.mockReturnValue(mockRoom); renderComponent(); - const readReceiptButton = screen.getByRole("button", { name: /jump to read receipt/i }); + const readReceiptButton = screen.getByRole("button", { name: "Jump to read receipt" }); expect(readReceiptButton).toBeInTheDocument(); await userEvent.click(readReceiptButton); @@ -973,52 +1040,6 @@ describe("", () => { }); }); - it("shows a modal before ignoring the user", async () => { - const originalCreateDialog = Modal.createDialog; - const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({ - finished: Promise.resolve([true]), - close: () => {}, - })); - - try { - mockClient.getIgnoredUsers.mockReturnValue([]); - renderComponent({ isIgnored: false }); - - await userEvent.click(screen.getByRole("button", { name: "Ignore" })); - expect(modalSpy).toHaveBeenCalled(); - expect(mockClient.setIgnoredUsers).toHaveBeenLastCalledWith([member.userId]); - } finally { - Modal.createDialog = originalCreateDialog; - } - }); - - it("cancels ignoring the user", async () => { - const originalCreateDialog = Modal.createDialog; - const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({ - finished: Promise.resolve([false]), - close: () => {}, - })); - - try { - mockClient.getIgnoredUsers.mockReturnValue([]); - renderComponent({ isIgnored: false }); - - await userEvent.click(screen.getByRole("button", { name: "Ignore" })); - expect(modalSpy).toHaveBeenCalled(); - expect(mockClient.setIgnoredUsers).not.toHaveBeenCalled(); - } finally { - Modal.createDialog = originalCreateDialog; - } - }); - - it("unignores the user", async () => { - mockClient.getIgnoredUsers.mockReturnValue([member.userId]); - renderComponent({ isIgnored: true }); - - await userEvent.click(screen.getByRole("button", { name: "Unignore" })); - expect(mockClient.setIgnoredUsers).toHaveBeenCalledWith([]); - }); - it.each([ ["for a RoomMember", member, member.getMxcAvatarUrl()], ["for a User", defaultUser, defaultUser.avatarUrl], @@ -1029,10 +1050,10 @@ describe("", () => { mocked(startDmOnFirstMessage).mockReturnValue(deferred.promise); renderComponent({ member }); - await userEvent.click(screen.getByText("Message")); + await userEvent.click(screen.getByRole("button", { name: "Send message" })); // Checking the attribute, because the button is a DIV and toBeDisabled() does not work. - expect(screen.getByText("Message")).toHaveAttribute("disabled"); + expect(screen.getByRole("button", { name: "Send message" })).toBeDisabled(); expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [ new DirectoryMember({ @@ -1048,7 +1069,7 @@ describe("", () => { }); // Checking the attribute, because the button is a DIV and toBeDisabled() does not work. - expect(screen.getByText("Message")).not.toHaveAttribute("disabled"); + expect(screen.getByRole("button", { name: "Send message" })).not.toBeDisabled(); }, ); }); @@ -1405,10 +1426,30 @@ describe("", () => { renderComponent({ member: defaultMemberWithPowerLevel }); - expect(screen.getByRole("heading", { name: /admin tools/i })).toBeInTheDocument(); - expect(screen.getByText(/disinvite from room/i)).toBeInTheDocument(); - expect(screen.getByText(/ban from room/i)).toBeInTheDocument(); - expect(screen.getByText(/remove recent messages/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Disinvite from room" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Ban from room" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Remove messages" })).toBeInTheDocument(); + }); + + it("should show BulkRedactDialog upon clicking the Remove messages button", async () => { + const spy = jest.spyOn(Modal, "createDialog"); + + mockClient.getRoom.mockReturnValue(mockRoom); + mockClient.getUserId.mockReturnValue("@arbitraryId:server"); + const mockMeMember = new RoomMember(mockRoom.roomId, mockClient.getUserId()!); + mockMeMember.powerLevel = 51; // defaults to 50 + const defaultMemberWithPowerLevel = { ...defaultMember, powerLevel: 0 } as RoomMember; + mockRoom.getMember.mockImplementation((userId) => + userId === mockClient.getUserId() ? mockMeMember : defaultMemberWithPowerLevel, + ); + + renderComponent({ member: defaultMemberWithPowerLevel }); + await userEvent.click(screen.getByRole("button", { name: "Remove messages" })); + + expect(spy).toHaveBeenCalledWith( + BulkRedactDialog, + expect.objectContaining({ member: defaultMemberWithPowerLevel }), + ); }); it("returns mute toggle button if conditions met", () => { @@ -1450,10 +1491,9 @@ describe("", () => { isUpdating: true, }); - const button = screen.getByText(/mute/i); + const button = screen.getByRole("button", { name: "Mute" }); expect(button).toBeInTheDocument(); - expect(button).toHaveAttribute("disabled"); - expect(button).toHaveAttribute("aria-disabled", "true"); + expect(button).toBeDisabled(); }); it("should not show mute button for one's own member", () => { diff --git a/test/components/views/right_panel/__snapshots__/RightPanelTabs-test.tsx.snap b/test/components/views/right_panel/__snapshots__/RightPanelTabs-test.tsx.snap new file mode 100644 index 000000000..36c3ccf6b --- /dev/null +++ b/test/components/views/right_panel/__snapshots__/RightPanelTabs-test.tsx.snap @@ -0,0 +1,119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Component renders the correct tabs 1`] = ` +
+ +
+`; + +exports[` Correct tab is active 1`] = ` +
+ +
+`; diff --git a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap index 784be9016..7c1b43e7c 100644 --- a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap +++ b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap @@ -3,7 +3,10 @@ exports[` has button to edit topic when expanded 1`] = `
has button to edit topic when expanded 1`] = `
- -
-
+ />
@@ -86,7 +71,7 @@ exports[` has button to edit topic when expanded 1`] = `

- - -
-
- - -
" diff --git a/test/utils/leave-behaviour-test.ts b/test/utils/leave-behaviour-test.ts index 48117caf4..e5c9f820d 100644 --- a/test/utils/leave-behaviour-test.ts +++ b/test/utils/leave-behaviour-test.ts @@ -16,6 +16,7 @@ limitations under the License. import { mocked, Mocked } from "jest-mock"; import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; +import { sleep } from "matrix-js-sdk/src/utils"; import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import { mkRoom, resetAsyncStoreWithClient, setupAsyncStoreWithClient, stubClient } from "../test-utils"; @@ -78,7 +79,7 @@ describe("leaveRoomBehaviour", () => { const expectDispatch = async (payload: T) => { const dispatcherSpy = jest.fn(); const dispatcherRef = defaultDispatcher.register(dispatcherSpy); - await new Promise((resolve) => setImmediate(resolve)); // Flush the dispatcher + await sleep(0); expect(dispatcherSpy).toHaveBeenCalledWith(payload); defaultDispatcher.unregister(dispatcherRef); }; diff --git a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts index 1298b1ff4..cac187b63 100644 --- a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts @@ -30,6 +30,7 @@ import { SyncState, } from "matrix-js-sdk/src/matrix"; import { EncryptedFile } from "matrix-js-sdk/src/types"; +import fetchMock from "fetch-mock-jest"; import { uploadFile } from "../../../src/ContentMessages"; import { createVoiceMessageContent } from "../../../src/utils/createVoiceMessageContent"; @@ -49,6 +50,7 @@ import { import { mkEvent, mkStubRoom, stubClient } from "../../test-utils"; import dis from "../../../src/dispatcher/dispatcher"; import { VoiceRecording } from "../../../src/audio/VoiceRecording"; +import { createAudioContext } from "../../../src/audio/compat"; jest.mock("../../../src/voice-broadcast/audio/VoiceBroadcastRecorder", () => ({ ...(jest.requireActual("../../../src/voice-broadcast/audio/VoiceBroadcastRecorder") as object), @@ -79,6 +81,11 @@ jest.mock("../../../src/utils/createVoiceMessageContent", () => ({ createVoiceMessageContent: jest.fn(), })); +jest.mock("../../../src/audio/compat", () => ({ + ...jest.requireActual("../../../src/audio/compat"), + createAudioContext: jest.fn(), +})); + describe("VoiceBroadcastRecording", () => { const roomId = "!room:example.com"; const uploadedUrl = "mxc://example.com/vb"; @@ -198,6 +205,19 @@ describe("VoiceBroadcastRecording", () => { }); }; + const mockAudioBufferSourceNode = { + addEventListener: jest.fn(), + connect: jest.fn(), + start: jest.fn(), + }; + const mockAudioContext = { + decodeAudioData: jest.fn(), + suspend: jest.fn(), + resume: jest.fn(), + createBufferSource: jest.fn().mockReturnValue(mockAudioBufferSourceNode), + currentTime: 1337, + }; + beforeEach(() => { client = stubClient(); room = mkStubRoom(roomId, "Test Room", client); @@ -265,6 +285,8 @@ describe("VoiceBroadcastRecording", () => { return null; }); + + mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext); }); afterEach(() => { @@ -546,12 +568,13 @@ describe("VoiceBroadcastRecording", () => { beforeEach(() => { mocked(client.sendMessage).mockRejectedValue("Error"); emitFirsChunkRecorded(); + fetchMock.get("media/error.mp3", 200); }); itShouldBeInState("connection_error"); it("should play a notification", () => { - expect(audioElement.play).toHaveBeenCalled(); + expect(mockAudioBufferSourceNode.start).toHaveBeenCalled(); }); describe("and the connection is back", () => { diff --git a/test/widgets/ManagedHybrid-test.ts b/test/widgets/ManagedHybrid-test.ts index b91db09dc..05093ed0d 100644 --- a/test/widgets/ManagedHybrid-test.ts +++ b/test/widgets/ManagedHybrid-test.ts @@ -14,38 +14,91 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { isManagedHybridWidgetEnabled } from "../../src/widgets/ManagedHybrid"; -import DMRoomMap from "../../src/utils/DMRoomMap"; +import { Room } from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; +import fetchMock from "fetch-mock-jest"; + +import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "../../src/widgets/ManagedHybrid"; import { stubClient } from "../test-utils"; import SdkConfig from "../../src/SdkConfig"; +import WidgetUtils from "../../src/utils/WidgetUtils"; +import { WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore"; + +jest.mock("../../src/utils/room/getJoinedNonFunctionalMembers", () => ({ + getJoinedNonFunctionalMembers: jest.fn().mockReturnValue([1, 2]), +})); describe("isManagedHybridWidgetEnabled", () => { - let dmRoomMap: DMRoomMap; + let room: Room; beforeEach(() => { - stubClient(); - dmRoomMap = { - getUserIdForRoomId: jest.fn().mockReturnValue("@user:server"), - } as unknown as DMRoomMap; - DMRoomMap.setShared(dmRoomMap); + const client = stubClient(); + room = new Room("!room:server", client, client.getSafeUserId()); }); it("should return false if widget_build_url is unset", () => { - expect(isManagedHybridWidgetEnabled("!room:server")).toBeFalsy(); + expect(isManagedHybridWidgetEnabled(room)).toBeFalsy(); }); - it("should return true for DMs when widget_build_url_ignore_dm is unset", () => { + it("should return true for 1-1 rooms when widget_build_url_ignore_dm is unset", () => { SdkConfig.put({ widget_build_url: "https://url", }); - expect(isManagedHybridWidgetEnabled("!room:server")).toBeTruthy(); + expect(isManagedHybridWidgetEnabled(room)).toBeTruthy(); }); - it("should return false for DMs when widget_build_url_ignore_dm is true", () => { + it("should return false for 1-1 rooms when widget_build_url_ignore_dm is true", () => { SdkConfig.put({ widget_build_url: "https://url", widget_build_url_ignore_dm: true, }); - expect(isManagedHybridWidgetEnabled("!room:server")).toBeFalsy(); + expect(isManagedHybridWidgetEnabled(room)).toBeFalsy(); + }); +}); + +describe("addManagedHybridWidget", () => { + let room: Room; + + beforeEach(() => { + const client = stubClient(); + room = new Room("!room:server", client, client.getSafeUserId()); + }); + + it("should noop if user lacks permission", async () => { + const logSpy = jest.spyOn(logger, "error").mockImplementation(); + jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(false); + + fetchMock.mockClear(); + await addManagedHybridWidget(room); + expect(logSpy).toHaveBeenCalledWith("User not allowed to modify widgets in !room:server"); + expect(fetchMock).toHaveBeenCalledTimes(0); + }); + + it("should noop if no widget_build_url", async () => { + jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true); + + fetchMock.mockClear(); + await addManagedHybridWidget(room); + expect(fetchMock).toHaveBeenCalledTimes(0); + }); + + it("should add the widget successfully", async () => { + fetchMock.get("https://widget-build-url/?roomId=!room:server", { + widget_id: "WIDGET_ID", + widget: { key: "value" }, + }); + jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true); + jest.spyOn(WidgetLayoutStore.instance, "canCopyLayoutToRoom").mockReturnValue(true); + const setRoomWidgetContentSpy = jest.spyOn(WidgetUtils, "setRoomWidgetContent").mockResolvedValue(); + SdkConfig.put({ + widget_build_url: "https://widget-build-url", + }); + + await addManagedHybridWidget(room); + expect(fetchMock).toHaveBeenCalledWith("https://widget-build-url?roomId=!room:server"); + expect(setRoomWidgetContentSpy).toHaveBeenCalledWith(room.client, room.roomId, "WIDGET_ID", { + "key": "value", + "io.element.managed_hybrid": true, + }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 3d5f96e1c..3118f598c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,13 +6,13 @@ "esModuleInterop": true, "module": "es2022", "moduleResolution": "node", - "target": "es2016", + "target": "es2018", "noUnusedLocals": true, "sourceMap": false, "outDir": "./lib", "declaration": true, "jsx": "react", - "lib": ["es2021", "dom", "dom.iterable"], + "lib": ["es2022", "dom", "dom.iterable"], "strict": true }, "include": [ diff --git a/yarn.lock b/yarn.lock index 83117c15e..4365b8022 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,10 +19,10 @@ resolved "https://registry.yarnpkg.com/@action-validator/core/-/core-0.6.0.tgz#8fbaf45562a5377140815b79cc1ac9f610ff63e5" integrity sha512-tPglwCr8Mm8SWzwnVewwFmqRx91F0WvMsM0BRAqH4CLalyGndm53Xvp+UcUSzswpk1wkjIDYI7RyEhWMLyPkig== -"@adobe/css-tools@^4.3.2": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" - integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== +"@adobe/css-tools@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63" + integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ== "@ampproject/remapping@^2.2.0": version "2.3.0" @@ -55,7 +55,7 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" chokidar "^3.4.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== @@ -63,7 +63,7 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13": +"@babel/code-frame@^7.10.4": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== @@ -71,7 +71,7 @@ "@babel/highlight" "^7.23.4" chalk "^2.4.2" -"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": +"@babel/code-frame@^7.12.13": version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== @@ -79,19 +79,6 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/code-frame@^7.23.5": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== - dependencies: - "@babel/highlight" "^7.24.2" - picocolors "^1.0.0" - -"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": - version "7.21.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" - integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== - "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" @@ -165,17 +152,7 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.3.tgz#86e6e83d95903fbe7613f448613b8b319f330a8e" - integrity sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg== - dependencies: - "@babel/types" "^7.23.3" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.24.7": +"@babel/generator@^7.23.3", "@babel/generator@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== @@ -185,13 +162,6 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-annotate-as-pure@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" @@ -207,17 +177,6 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-compilation-targets@^7.20.7": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" - integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== - dependencies: - "@babel/compat-data" "^7.21.5" - "@babel/helper-validator-option" "^7.21.0" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" - "@babel/helper-compilation-targets@^7.22.15": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" @@ -240,21 +199,6 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6": - version "7.21.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz#205b26330258625ef8869672ebca1e0dee5a0f02" - integrity sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.21.5" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.21.5" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.21.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/helper-split-export-declaration" "^7.18.6" - semver "^6.3.0" - "@babel/helper-create-class-features-plugin@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz#2eaed36b3a1c11c53bdf80d53838b293c52f5b3b" @@ -290,27 +234,14 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.21.5", "@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-environment-visitor@^7.24.7": +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== dependencies: "@babel/types" "^7.24.7" -"@babel/helper-function-name@^7.21.0", "@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-function-name@^7.24.7": +"@babel/helper-function-name@^7.23.0", "@babel/helper-function-name@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== @@ -318,27 +249,13 @@ "@babel/template" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-hoist-variables@^7.24.7": +"@babel/helper-hoist-variables@^7.22.5", "@babel/helper-hoist-variables@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== dependencies: "@babel/types" "^7.24.7" -"@babel/helper-member-expression-to-functions@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz#3b1a009af932e586af77c1030fba9ee0bde396c0" - integrity sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg== - dependencies: - "@babel/types" "^7.21.5" - "@babel/helper-member-expression-to-functions@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz#67613d068615a70e4ed5101099affc7a41c5225f" @@ -384,13 +301,6 @@ "@babel/helper-split-export-declaration" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7" -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - "@babel/helper-optimise-call-expression@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" @@ -403,11 +313,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== -"@babel/helper-plugin-utils@^7.20.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - "@babel/helper-remap-async-to-generator@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" @@ -417,18 +322,6 @@ "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-wrap-function" "^7.24.7" -"@babel/helper-replace-supers@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c" - integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg== - dependencies: - "@babel/helper-environment-visitor" "^7.21.5" - "@babel/helper-member-expression-to-functions" "^7.21.5" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.5" - "@babel/types" "^7.21.5" - "@babel/helper-replace-supers@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" @@ -453,13 +346,6 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" - integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== - dependencies: - "@babel/types" "^7.20.0" - "@babel/helper-skip-transparent-expression-wrappers@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" @@ -468,52 +354,23 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-split-export-declaration@^7.18.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" - integrity sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q== - dependencies: - "@babel/types" "^7.24.5" - -"@babel/helper-split-export-declaration@^7.24.7": +"@babel/helper-split-export-declaration@^7.22.6", "@babel/helper-split-export-declaration@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== dependencies: "@babel/types" "^7.24.7" -"@babel/helper-string-parser@^7.22.5": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" - integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== - -"@babel/helper-string-parser@^7.24.1", "@babel/helper-string-parser@^7.24.7": +"@babel/helper-string-parser@^7.22.5", "@babel/helper-string-parser@^7.24.1", "@babel/helper-string-parser@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== -"@babel/helper-validator-identifier@^7.22.20": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" - integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== - -"@babel/helper-validator-identifier@^7.24.5", "@babel/helper-validator-identifier@^7.24.7": +"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.5", "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== -"@babel/helper-validator-option@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" - integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== - "@babel/helper-validator-option@^7.23.5", "@babel/helper-validator-option@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" @@ -555,17 +412,7 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/highlight@^7.23.4": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" - integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/highlight@^7.24.2", "@babel/highlight@^7.24.7": +"@babel/highlight@^7.23.4", "@babel/highlight@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== @@ -575,7 +422,7 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.23.3", "@babel/parser@^7.24.0", "@babel/parser@^7.24.7": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.23.3", "@babel/parser@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== @@ -612,14 +459,6 @@ "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-proposal-class-properties@^7.12.1": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-proposal-export-default-from@^7.12.1": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.7.tgz#0b539c46b8ac804f694e338f803c8354c0f788b6" @@ -628,25 +467,6 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-export-default-from" "^7.24.7" -"@babel/plugin-proposal-numeric-separator@^7.12.7": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" - integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.12.1": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" - integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== - dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.7" - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" @@ -861,7 +681,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-class-properties@^7.24.7": +"@babel/plugin-transform-class-properties@^7.12.1", "@babel/plugin-transform-class-properties@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834" integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w== @@ -1051,7 +871,7 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.24.7": +"@babel/plugin-transform-numeric-separator@^7.12.7", "@babel/plugin-transform-numeric-separator@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz#bea62b538c80605d8a0fac9b40f48e97efa7de63" integrity sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA== @@ -1059,7 +879,7 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.24.7": +"@babel/plugin-transform-object-rest-spread@^7.12.1", "@babel/plugin-transform-object-rest-spread@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz#d13a2b93435aeb8a197e115221cab266ba6e55d6" integrity sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q== @@ -1094,13 +914,6 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.20.7": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db" - integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-transform-parameters@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz#5881f0ae21018400e320fc7eb817e529d1254b68" @@ -1405,7 +1218,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -1413,31 +1226,13 @@ regenerator-runtime "^0.14.0" "@babel/runtime@^7.13.10": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e" - integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw== + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - -"@babel/template@^7.22.15": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - -"@babel/template@^7.24.7": +"@babel/template@^7.22.15", "@babel/template@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== @@ -1455,23 +1250,7 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.18.5", "@babel/traverse@^7.21.5", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.3.tgz#26ee5f252e725aa7aca3474aa5b324eaf7908b5b" - integrity sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.3" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.3" - "@babel/types" "^7.23.3" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.24.7": +"@babel/traverse@^7.18.5", "@babel/traverse@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== @@ -1487,7 +1266,23 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.5", "@babel/types@^7.3.3": +"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.3.tgz#26ee5f252e725aa7aca3474aa5b324eaf7908b5b" + integrity sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.3" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.3" + "@babel/types" "^7.23.3" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.3.3": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== @@ -1496,7 +1291,7 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5": +"@babel/types@^7.22.15", "@babel/types@^7.22.19": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" integrity sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ== @@ -1505,7 +1300,7 @@ "@babel/helper-validator-identifier" "^7.24.5" to-fast-properties "^2.0.0" -"@babel/types@^7.22.5", "@babel/types@^7.24.7", "@babel/types@^7.4.4": +"@babel/types@^7.23.0", "@babel/types@^7.23.3", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.24.7", "@babel/types@^7.4.4": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== @@ -1514,15 +1309,6 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" -"@babel/types@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.3.tgz#d5ea892c07f2ec371ac704420f4dcdb07b5f9598" - integrity sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1569,11 +1355,6 @@ resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b" integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== - "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1582,9 +1363,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.10.0": - version "4.10.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.1.tgz#361461e5cb3845d874e61731c11cfedd664d83a0" - integrity sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA== + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== "@eslint-community/regexpp@^4.6.1": version "4.8.0" @@ -1626,41 +1407,41 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== -"@floating-ui/core@^1.0.0": - version "1.6.2" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.2.tgz#d37f3e0ac1f1c756c7de45db13303a266226851a" - integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg== +"@floating-ui/core@^1.6.0": + version "1.6.4" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.4.tgz#0140cf5091c8dee602bff9da5ab330840ff91df6" + integrity sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA== dependencies: - "@floating-ui/utils" "^0.2.0" + "@floating-ui/utils" "^0.2.4" "@floating-ui/dom@^1.0.0": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" - integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.7.tgz#85d22f731fcc5b209db504478fb1df5116a83015" + integrity sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng== dependencies: - "@floating-ui/core" "^1.0.0" - "@floating-ui/utils" "^0.2.0" + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.4" -"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.0.8", "@floating-ui/react-dom@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff" - integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA== +"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.0.8": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0" + integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg== dependencies: "@floating-ui/dom" "^1.0.0" -"@floating-ui/react@^0.26.9": - version "0.26.16" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.16.tgz#3415a087f452165161c2d313d1d57e8142894679" - integrity sha512-HEf43zxZNAI/E781QIVpYSF3K2VH4TTYZpqecjdsFkjsaU1EbaWcM++kw0HXFffj7gDUcBFevX8s0rQGQpxkow== +"@floating-ui/react@0.26.11", "@floating-ui/react@^0.26.9": + version "0.26.11" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.11.tgz#226d3fec890de439443b62f3138ef7de052b0998" + integrity sha512-fo01Cu+jzLDVG/AYAV2OtV6flhXvxP5rDaR1Fk8WWhtsFqwk478Dr2HGtB8s0HqQCsFWVbdHYpPjMiQiR/A9VA== dependencies: - "@floating-ui/react-dom" "^2.1.0" + "@floating-ui/react-dom" "^2.0.0" "@floating-ui/utils" "^0.2.0" tabbable "^6.0.0" -"@floating-ui/utils@^0.2.0": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5" - integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c" + integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA== "@humanwhocodes/config-array@^0.11.14": version "0.11.14" @@ -1999,10 +1780,10 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== -"@matrix-org/analytics-events@^0.21.0": - version "0.21.0" - resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.21.0.tgz#1de19a6a765f179c01199e72c9c461dc7120fe1a" - integrity sha512-K0E9eje03o3pYc8C93XFTu6DTgNdsVNvdkH7rsFGiHkc15WQybKFyHR7quuuV42jrzGINWpFou0faCWcDBdNbQ== +"@matrix-org/analytics-events@^0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.24.0.tgz#21a64537ac975b18e1eb13d9fd0bdc7d448a6039" + integrity sha512-3FDdtqZ+5cMqVffWjFNOIQ7RDFN6XS11kqdtN2ps8uvq5ce8gT0yXQvK37WeKWKZZ5QAKeoMzGhud+lsVcb1xg== "@matrix-org/emojibase-bindings@^1.1.2": version "1.1.3" @@ -2012,15 +1793,15 @@ emojibase "^15.0.0" emojibase-data "^15.0.0" -"@matrix-org/matrix-sdk-crypto-wasm@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-5.0.0.tgz#f45a7bccaad218c05bcf9e7c8ca783c9d9a07af4" - integrity sha512-37ASjCKSTU5ycGfkP+LUXG4Ok6OAf6vE+1qU6uwWhe6FwadCS3vVWzJYd/3d9BQFwsx4GhFTIAXrW4iLG85rmQ== +"@matrix-org/matrix-sdk-crypto-wasm@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-6.1.0.tgz#1cedf2bcbd6795e297fd45ea4a33f2c8c5204fdd" + integrity sha512-8Wn4TT9PEJswfE8+6mA60JHrxyiWYXfM4EM5800tLz+Rl9QRGk9JDF0o0cTb26v6bfXTa3/pCGWAkUVk0ROPEw== -"@matrix-org/matrix-wysiwyg@2.17.0": - version "2.17.0" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.17.0.tgz#68c83da17826fb43828f0c1ddd8d6e0b9d155ae5" - integrity sha512-PZGSrNqKCSdUnyUVglEvHrV8uowU3JuWUlYYKBslYnnIrJHw9aS2nnCpLVqwACFD6N82+L+Net8ME9i3qy7BGQ== +"@matrix-org/matrix-wysiwyg@2.37.4": + version "2.37.4" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.37.4.tgz#bd9b46051a21c9986477e3a83a1417b1ee926d81" + integrity sha512-4OtBWAHNAOu9P5C6jOIeHlu4ChwV2YusxnbGuN20IceC4bT2h38flZQgm0x9/jgHfF0LwnKUwKXsxtRoq8xW0g== "@matrix-org/olm@3.2.15": version "3.2.15" @@ -2035,9 +1816,9 @@ "@babel/runtime" "^7.17.9" "@matrix-org/spec@^1.7.0": - version "1.10.1" - resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.10.1.tgz#6c62a7ee4524224c29fe39e67b33983e70c99944" - integrity sha512-ryNSzJkaJi/fwp6AQ6ujS9oqJtw60e+/+llIzwJhbuWTr1V07B6KfUA44+bFrudIFmfghdOuxRfzUnWOQ2K6gw== + version "1.11.0" + resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.11.0.tgz#73864eab965c81a4c8c48ca0da8c9d94e8efd5dc" + integrity sha512-80000pCXpUnt3ue910uZY70kLo9b7pNfrUrlXY5smpDjfycEs1oztUAriPAnKxMp31gUqt9/tfjmBM2H/LNCZw== "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" @@ -2089,15 +1870,15 @@ tslib "^2.0.0" "@peculiar/webcrypto@^1.4.3": - version "1.4.6" - resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.6.tgz#607af294c4f205efeeb172aa32cb20024fe4aecf" - integrity sha512-YBcMfqNSwn3SujUJvAaySy5tlYbYm6tVt9SKoXu8BaTdKGROiJDgPR3TXpZdAKUfklzm3lRapJEAltiMQtBgZg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz#9e57174c02c1291051c553600347e12b81469e10" + integrity sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg== dependencies: "@peculiar/asn1-schema" "^2.3.8" "@peculiar/json-schema" "^1.1.12" pvtsutils "^1.3.5" tslib "^2.6.2" - webcrypto-core "^1.7.9" + webcrypto-core "^1.8.0" "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -2105,11 +1886,11 @@ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@playwright/test@^1.40.1": - version "1.44.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.1.tgz#cc874ec31342479ad99838040e99b5f604299bcb" - integrity sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q== + version "1.45.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.45.2.tgz#e1b8512e20916720de1c5f5e89a362a252ea78ca" + integrity sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ== dependencies: - playwright "1.44.1" + playwright "1.45.2" "@radix-ui/primitive@1.0.1": version "1.0.1" @@ -2118,24 +1899,27 @@ dependencies: "@babel/runtime" "^7.13.10" -"@radix-ui/react-arrow@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" - integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA== +"@radix-ui/primitive@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" + integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== + +"@radix-ui/react-arrow@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz#744f388182d360b86285217e43b6c63633f39e7a" + integrity sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-primitive" "2.0.0" -"@radix-ui/react-collection@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" - integrity sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA== +"@radix-ui/react-collection@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" + integrity sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" "@radix-ui/react-compose-refs@1.0.1": version "1.0.1" @@ -2144,18 +1928,22 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-compose-refs@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" + integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== + "@radix-ui/react-context-menu@^2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.1.5.tgz#1bdbd72761439f9166f75dc4598f276265785c83" - integrity sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g== + version "2.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.1.tgz#a2c7812336a40cd22900c888336ad6e1adc6a1bc" + integrity sha512-wvMKKIeb3eOrkJ96s722vcidZ+2ZNfcYZWBPRHIB1VWrF+fiF851Io6LX0kmK5wTDQFKdulCCKJk2c3SBaQHvA== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-menu" "2.0.6" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-menu" "2.1.1" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-context@1.0.1": version "1.0.1" @@ -2164,76 +1952,73 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-context@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" + integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== + "@radix-ui/react-dialog@^1.0.4": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300" - integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-dismissable-layer" "1.0.5" - "@radix-ui/react-focus-guards" "1.0.1" - "@radix-ui/react-focus-scope" "1.0.4" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-portal" "1.0.4" - "@radix-ui/react-presence" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-slot" "1.0.2" - "@radix-ui/react-use-controllable-state" "1.0.1" + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44" + integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-focus-guards" "1.1.0" + "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" aria-hidden "^1.1.1" - react-remove-scroll "2.5.5" + react-remove-scroll "2.5.7" -"@radix-ui/react-direction@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" - integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA== - dependencies: - "@babel/runtime" "^7.13.10" +"@radix-ui/react-direction@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc" + integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg== -"@radix-ui/react-dismissable-layer@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" - integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== +"@radix-ui/react-dismissable-layer@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz#2cd0a49a732372513733754e6032d3fb7988834e" + integrity sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-escape-keydown" "1.0.3" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown" "1.1.0" "@radix-ui/react-dropdown-menu@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz#cdf13c956c5e263afe4e5f3587b3071a25755b63" - integrity sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-menu" "2.0.6" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-controllable-state" "1.0.1" - -"@radix-ui/react-focus-guards@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" - integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA== - dependencies: - "@babel/runtime" "^7.13.10" + version "2.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz#3dc578488688250dbbe109d9ff2ca28a9bca27ec" + integrity sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-menu" "2.1.1" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + +"@radix-ui/react-focus-guards@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13" + integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw== -"@radix-ui/react-focus-scope@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" - integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== +"@radix-ui/react-focus-scope@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2" + integrity sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-form@^0.0.3": version "0.0.3" @@ -2248,13 +2033,12 @@ "@radix-ui/react-label" "2.0.2" "@radix-ui/react-primitive" "1.0.3" -"@radix-ui/react-id@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" - integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ== +"@radix-ui/react-id@1.0.1", "@radix-ui/react-id@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed" + integrity sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-label@2.0.2": version "2.0.2" @@ -2264,64 +2048,61 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" -"@radix-ui/react-menu@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e" - integrity sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-collection" "1.0.3" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-dismissable-layer" "1.0.5" - "@radix-ui/react-focus-guards" "1.0.1" - "@radix-ui/react-focus-scope" "1.0.4" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-popper" "1.1.3" - "@radix-ui/react-portal" "1.0.4" - "@radix-ui/react-presence" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-roving-focus" "1.0.4" - "@radix-ui/react-slot" "1.0.2" - "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-menu@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.1.tgz#bd623ace0e1ae1ac78023a505fec0541d59fb346" + integrity sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-focus-guards" "1.1.0" + "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-roving-focus" "1.1.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-use-callback-ref" "1.1.0" aria-hidden "^1.1.1" - react-remove-scroll "2.5.5" + react-remove-scroll "2.5.7" -"@radix-ui/react-popper@1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" - integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w== +"@radix-ui/react-popper@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a" + integrity sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg== dependencies: - "@babel/runtime" "^7.13.10" "@floating-ui/react-dom" "^2.0.0" - "@radix-ui/react-arrow" "1.0.3" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-layout-effect" "1.0.1" - "@radix-ui/react-use-rect" "1.0.1" - "@radix-ui/react-use-size" "1.0.1" - "@radix-ui/rect" "1.0.1" - -"@radix-ui/react-portal@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" - integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + "@radix-ui/react-arrow" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-use-rect" "1.1.0" + "@radix-ui/react-use-size" "1.1.0" + "@radix-ui/rect" "1.1.0" + +"@radix-ui/react-portal@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.1.tgz#1957f1eb2e1aedfb4a5475bd6867d67b50b1d15f" + integrity sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-presence@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" - integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg== +"@radix-ui/react-presence@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.0.tgz#227d84d20ca6bfe7da97104b1a8b48a833bfb478" + integrity sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-primitive@1.0.3": version "1.0.3" @@ -2331,31 +2112,44 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" -"@radix-ui/react-roving-focus@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" - integrity sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ== +"@radix-ui/react-primitive@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" + integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-collection" "1.0.3" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-direction" "1.0.1" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-slot" "1.1.0" + +"@radix-ui/react-progress@^1.0.3": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.0.tgz#28c267885ec154fc557ec7a66cb462787312f7e2" + integrity sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg== + dependencies: + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + +"@radix-ui/react-roving-focus@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e" + integrity sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-separator@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.0.3.tgz#be5a931a543d5726336b112f465f58585c04c8aa" - integrity sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e" + integrity sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-primitive" "2.0.0" -"@radix-ui/react-slot@1.0.2", "@radix-ui/react-slot@^1.0.2": +"@radix-ui/react-slot@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== @@ -2363,167 +2157,151 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.0.2": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84" + integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-tooltip@^1.0.6": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz#8f55070f852e7e7450cc1d9210b793d2e5a7686e" - integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw== + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz#c42db2ffd7dcc6ff3d65407c8cb70490288f518d" + integrity sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-visually-hidden" "1.1.0" + +"@radix-ui/react-use-callback-ref@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1" + integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw== + +"@radix-ui/react-use-controllable-state@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0" + integrity sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-context" "1.0.1" - "@radix-ui/react-dismissable-layer" "1.0.5" - "@radix-ui/react-id" "1.0.1" - "@radix-ui/react-popper" "1.1.3" - "@radix-ui/react-portal" "1.0.4" - "@radix-ui/react-presence" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-slot" "1.0.2" - "@radix-ui/react-use-controllable-state" "1.0.1" - "@radix-ui/react-visually-hidden" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.1.0" -"@radix-ui/react-use-callback-ref@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" - integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ== +"@radix-ui/react-use-escape-keydown@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754" + integrity sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw== dependencies: - "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.1.0" -"@radix-ui/react-use-controllable-state@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" - integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA== +"@radix-ui/react-use-layout-effect@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" + integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== + +"@radix-ui/react-use-rect@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88" + integrity sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/rect" "1.1.0" -"@radix-ui/react-use-escape-keydown@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" - integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== +"@radix-ui/react-use-size@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz#b4dba7fbd3882ee09e8d2a44a3eed3a7e555246b" + integrity sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-use-layout-effect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" - integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ== +"@radix-ui/react-visually-hidden@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2" + integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ== dependencies: - "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "2.0.0" -"@radix-ui/react-use-rect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2" - integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw== +"@radix-ui/rect@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" + integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== + +"@sentry-internal/browser-utils@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.16.0.tgz#182931f169a586dde50cf255237b129aad00dde7" + integrity sha512-40lzNy5F6dUFCN85AGThBxHPQLSwoNhZM2hWqhAR5rZ3Yed0uBaKlm4aNJCeeUB9l4kd0sH0In+i9Nqu6TGKrw== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/rect" "1.0.1" + "@sentry/core" "8.16.0" + "@sentry/types" "8.16.0" + "@sentry/utils" "8.16.0" -"@radix-ui/react-use-size@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2" - integrity sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g== +"@sentry-internal/feedback@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.16.0.tgz#dc8a3b807a37d0df136e62937e87ac23ce2ce6a8" + integrity sha512-BmRazZKl6iiVSg6eybUNOI1ve4eZqYpJYjkX48Jedn+7iZg7z12MNYl6IWPFBcN+sg+clf4wiKDr/SYS0yNemQ== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-use-layout-effect" "1.0.1" + "@sentry/core" "8.16.0" + "@sentry/types" "8.16.0" + "@sentry/utils" "8.16.0" -"@radix-ui/react-visually-hidden@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac" - integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA== +"@sentry-internal/replay-canvas@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.16.0.tgz#c6501dd9f7e5dac1399978cc9e2797eb281a8f70" + integrity sha512-Bjh6pCDLZIPAPU2dNvJfI7BQV16rsRtYcylJgkGamjf8IcaBu7r/Whsvt1q34xO29xc0ISlp+0xG+YAdN1690Q== dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-primitive" "1.0.3" + "@sentry-internal/replay" "8.16.0" + "@sentry/core" "8.16.0" + "@sentry/types" "8.16.0" + "@sentry/utils" "8.16.0" -"@radix-ui/rect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f" - integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ== +"@sentry-internal/replay@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.16.0.tgz#5bf564d7293d4fb4993327567e9ad12079ceb951" + integrity sha512-JT/wmYU2JPtl8Ldl9oml/25Yz6C5wG+SpylDeUx4mPh728E/iI9vesIc2652J/0xots/DZXe4K6K5nYjdFtEcQ== dependencies: - "@babel/runtime" "^7.13.10" + "@sentry-internal/browser-utils" "8.16.0" + "@sentry/core" "8.16.0" + "@sentry/types" "8.16.0" + "@sentry/utils" "8.16.0" + +"@sentry/browser@^8.0.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.16.0.tgz#af9b7b7556198d6de03cbc41b7abb5a16ecfc342" + integrity sha512-8Fxmk2aFWRixi2IKixiJR10Du34yb13HYr2iRw1haPKb5ZKa6CFA+XAnSzwpPZxO0RSHuPQR06YNkXaQ8fRAQQ== + dependencies: + "@sentry-internal/browser-utils" "8.16.0" + "@sentry-internal/feedback" "8.16.0" + "@sentry-internal/replay" "8.16.0" + "@sentry-internal/replay-canvas" "8.16.0" + "@sentry/core" "8.16.0" + "@sentry/types" "8.16.0" + "@sentry/utils" "8.16.0" + +"@sentry/core@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.16.0.tgz#cf2f4e572240983ec7e9fa083cc1ffce3147f20b" + integrity sha512-l9mQgm5OqnykvZMh6PmJ/9ygW4qLyEFop+pQH/uM5zQCZQvEa7rvAd9QXKHdbVKq1CxJa/nJiByc8wPWxsftGQ== + dependencies: + "@sentry/types" "8.16.0" + "@sentry/utils" "8.16.0" -"@sentry-internal/feedback@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.116.0.tgz#f1352b1a0d5fd7b7167775330ccf03bcc1b7892b" - integrity sha512-tmfO+RTCrhIWMs3yg8X0axhbjWRZLsldSfoXBgfjNCk/XwkYiVGp7WnYVbb+IO+01mHCsis9uaYOBggLgFRB5Q== - dependencies: - "@sentry/core" "7.116.0" - "@sentry/types" "7.116.0" - "@sentry/utils" "7.116.0" - -"@sentry-internal/replay-canvas@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.116.0.tgz#1cd4a85f99dd3cd61120e087232f5cbea21d5eb2" - integrity sha512-Sy0ydY7A97JY/IFTIj8U25kHqR5rL9oBk3HFE5EK9Phw56irVhHzEwLWae0jlFeCQEWoBYqpPgO5vXsaYzrWvw== - dependencies: - "@sentry/core" "7.116.0" - "@sentry/replay" "7.116.0" - "@sentry/types" "7.116.0" - "@sentry/utils" "7.116.0" - -"@sentry-internal/tracing@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.116.0.tgz#af3e4e264c440aa5525b5877a10b9a0f870b40e3" - integrity sha512-y5ppEmoOlfr77c/HqsEXR72092qmGYS4QE5gSz5UZFn9CiinEwGfEorcg2xIrrCuU7Ry/ZU2VLz9q3xd04drRA== - dependencies: - "@sentry/core" "7.116.0" - "@sentry/types" "7.116.0" - "@sentry/utils" "7.116.0" - -"@sentry/browser@^7.0.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.116.0.tgz#950c1a9672bf886c556c2c7b9198b90189e3f0c2" - integrity sha512-2aosATT5qE+QLKgTmyF9t5Emsluy1MBczYNuPmLhDxGNfB+MA86S8u7Hb0CpxdwjS0nt14gmbiOtJHoeAF3uTw== - dependencies: - "@sentry-internal/feedback" "7.116.0" - "@sentry-internal/replay-canvas" "7.116.0" - "@sentry-internal/tracing" "7.116.0" - "@sentry/core" "7.116.0" - "@sentry/integrations" "7.116.0" - "@sentry/replay" "7.116.0" - "@sentry/types" "7.116.0" - "@sentry/utils" "7.116.0" - -"@sentry/core@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.116.0.tgz#7cff43134878a696b2b3b981ae384ec3db9ac8c3" - integrity sha512-J6Wmjjx+o7RwST0weTU1KaKUAlzbc8MGkJV1rcHM9xjNTWTva+nrcCM3vFBagnk2Gm/zhwv3h0PvWEqVyp3U1Q== - dependencies: - "@sentry/types" "7.116.0" - "@sentry/utils" "7.116.0" - -"@sentry/integrations@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.116.0.tgz#b641342249da76cd2feb2fb5511424b66f967449" - integrity sha512-UZb60gaF+7veh1Yv79RiGvgGYOnU6xA97H+hI6tKgc1uT20YpItO4X56Vhp0lvyEyUGFZzBRRH1jpMDPNGPkqw== - dependencies: - "@sentry/core" "7.116.0" - "@sentry/types" "7.116.0" - "@sentry/utils" "7.116.0" - localforage "^1.8.1" - -"@sentry/replay@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.116.0.tgz#cde921133c8927be92d60baf03c2b0aea73380f1" - integrity sha512-OrpDtV54pmwZuKp3g7PDiJg6ruRMJKOCzK08TF7IPsKrr4x4UQn56rzMOiABVuTjuS8lNfAWDar6c6vxXFz5KA== - dependencies: - "@sentry-internal/tracing" "7.116.0" - "@sentry/core" "7.116.0" - "@sentry/types" "7.116.0" - "@sentry/utils" "7.116.0" - -"@sentry/types@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.116.0.tgz#0be3434e7e53c86db4993e668af1c3a65bfb7519" - integrity sha512-QCCvG5QuQrwgKzV11lolNQPP2k67Q6HHD9vllZ/C4dkxkjoIym8Gy+1OgAN3wjsR0f/kG9o5iZyglgNpUVRapQ== - -"@sentry/utils@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.116.0.tgz#f32463ab10f76f464274233a9df202e5357d17ff" - integrity sha512-Vn9fcvwTq91wJvCd7WTMWozimqMi+dEZ3ie3EICELC2diONcN16ADFdzn65CQQbYwmUzRjN9EjDN2k41pKZWhQ== - dependencies: - "@sentry/types" "7.116.0" +"@sentry/types@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.16.0.tgz#a9ae39cffd50a0bdba0556a1596fb135d035cf26" + integrity sha512-cIRsn7gWGVaWHgCniBWA0N8PNwzDYibhjyjPRTMxUjuZCT37i7zxByKKmd9u4TpRIJ64MyirNyM0O6T0A26fpg== + +"@sentry/utils@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.16.0.tgz#5d1c9fb6cd562660b507c6647e6437282bef939a" + integrity sha512-tltCf2DVzz5TiYjxu/Rxbc9Qmm04893MFshV97jOTBcQeO2AAZBEl5rAoTCv1P08y7Yg+KiVwCx9Zj2x5U80/g== + dependencies: + "@sentry/types" "8.16.0" "@sinclair/typebox@^0.27.8": version "0.27.8" @@ -2544,98 +2322,6 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@storybook/channels@8.1.5": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-8.1.5.tgz#d00d033d318cf202ece1de728e55e85f82242e74" - integrity sha512-R+puP4tWYzQUbpIp8sX6U5oI+ZUevVOaFxXGaAN3PRXjIRC38oKTVWzj/G6GdziVFzN6rDn+JsYPmiRMYo1sYg== - dependencies: - "@storybook/client-logger" "8.1.5" - "@storybook/core-events" "8.1.5" - "@storybook/global" "^5.0.0" - telejson "^7.2.0" - tiny-invariant "^1.3.1" - -"@storybook/client-logger@8.1.5": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.1.5.tgz#aa4a6ce4ca46fdfe12539e571f9059a479c8ae43" - integrity sha512-zd+aENXnOHsxBATppELmhw/UywLzCxQjz/8i/xkUjeTRB4Ggp0hJlOUdJUEdIJz631ydyytfvM70ktBj9gMl1w== - dependencies: - "@storybook/global" "^5.0.0" - -"@storybook/core-events@8.1.5": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-8.1.5.tgz#d921984e12b27aaaa623499a7ac0c3eea5e96264" - integrity sha512-fgwbrHoLtSX6kfmamTGJqD+KfuEgun8cc4mWKZK094ByaqbSjhnOyeYO1sfVk8qst7QTFlOfhLAUe4cz1z149A== - dependencies: - "@storybook/csf" "^0.1.7" - ts-dedent "^2.0.0" - -"@storybook/csf@^0.1.7": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.7.tgz#dcc6c16a353bc09c8c619ba1a23ba93b2aab0b9d" - integrity sha512-53JeLZBibjQxi0Ep+/AJTfxlofJlxy1jXcSKENlnKxHjWEYyHQCumMP5yTFjf7vhNnMjEpV3zx6t23ssFiGRyw== - dependencies: - type-fest "^2.19.0" - -"@storybook/global@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@storybook/global/-/global-5.0.0.tgz#b793d34b94f572c1d7d9e0f44fac4e0dbc9572ed" - integrity sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ== - -"@storybook/icons@^1.2.5": - version "1.2.9" - resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-1.2.9.tgz#bb4a51a79e186b62e2dd0e04928b8617ac573838" - integrity sha512-cOmylsz25SYXaJL/gvTk/dl3pyk7yBFRfeXTsHvTA3dfhoU/LWSq0NKL9nM7WBasJyn6XPSGnLS4RtKXLw5EUg== - -"@storybook/manager-api@^8.1.1": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-8.1.5.tgz#1f1a8875cbc19fad5435f670943207158dc76551" - integrity sha512-iVP7FOKDf9L7zWCb8C2XeZjWSILS3hHeNwILvd9YSX9dg9du41kJYahsAHxDCR/jp/gv0ZM/V0vuHzi+naVPkQ== - dependencies: - "@storybook/channels" "8.1.5" - "@storybook/client-logger" "8.1.5" - "@storybook/core-events" "8.1.5" - "@storybook/csf" "^0.1.7" - "@storybook/global" "^5.0.0" - "@storybook/icons" "^1.2.5" - "@storybook/router" "8.1.5" - "@storybook/theming" "8.1.5" - "@storybook/types" "8.1.5" - dequal "^2.0.2" - lodash "^4.17.21" - memoizerific "^1.11.3" - store2 "^2.14.2" - telejson "^7.2.0" - ts-dedent "^2.0.0" - -"@storybook/router@8.1.5": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-8.1.5.tgz#e1dd831136e874df833286fd76554958af6132fa" - integrity sha512-DCwvAswlbLhQu6REPV04XNRhtPvsrRqHjMHKzjlfs+qYJWY7Egkofy05qlegqjkMDve33czfnRGBm0C16IydkA== - dependencies: - "@storybook/client-logger" "8.1.5" - memoizerific "^1.11.3" - qs "^6.10.0" - -"@storybook/theming@8.1.5": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.1.5.tgz#8eb0718907ec443cfca1b73491f5e99df65930af" - integrity sha512-E4z1t49fMbVvd/t2MSL0Ecp5zbqsU/QfWBX/eorJ+m+Xc9skkwwG5qf/FnP9x4RZ9KaX8U8+862t0eafVvf4Tw== - dependencies: - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@storybook/client-logger" "8.1.5" - "@storybook/global" "^5.0.0" - memoizerific "^1.11.3" - -"@storybook/types@8.1.5": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@storybook/types/-/types-8.1.5.tgz#627cac55e8034deed4b763327ff938c84c541a05" - integrity sha512-/PfAZh1xtXN2MvAZZKpiL/nPkC3bZj8BQ7P7z5a/aQarP+y7qdXuoitYQ6oOH3rkaiYywmkWzA/y4iW70KXLKg== - dependencies: - "@storybook/channels" "8.1.5" - "@types/express" "^4.7.0" - file-system-cache "2.3.0" - "@testing-library/dom@^8.0.0": version "8.20.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.0.tgz#914aa862cef0f5e89b98cc48e3445c4c921010f6" @@ -2665,11 +2351,11 @@ pretty-format "^27.0.2" "@testing-library/jest-dom@^6.0.0": - version "6.4.5" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.5.tgz#badb40296477149136dabef32b572ddd3b56adf1" - integrity sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A== + version "6.4.6" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz#ec1df8108651bed5475534955565bed88c6732ce" + integrity sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w== dependencies: - "@adobe/css-tools" "^4.3.2" + "@adobe/css-tools" "^4.4.0" "@babel/runtime" "^7.9.2" aria-query "^5.0.0" chalk "^3.0.0" @@ -2705,11 +2391,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -2798,6 +2479,11 @@ resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.4.tgz#e3e331b7e0d5496873d417839f3b2bbcf555bb73" integrity sha512-aqBg5oAGo/qh/+wxUfuMadDu2WO0MEWOblyzwaM1Ske2xilUxBfgPqapAFVAfrVTDMVwa0UMarzGot8m64IAzA== +"@types/css-tree@^2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@types/css-tree/-/css-tree-2.3.8.tgz#0eabc115e45051b2f7abe51ee1531074b234ed19" + integrity sha512-zABG3nI2UENsx7AQv63tI5/ptoAG/7kQR1H0OvG+WTWYHOR5pfAT3cGgC8SdyCrgX/TTxJBZNmx82IjCXs1juQ== + "@types/diff-match-patch@^1.0.32": version "1.0.36" resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz#dcef10a69d357fe9d43ac4ff2eca6b85dbf466af" @@ -2823,7 +2509,7 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@^4.17.21", "@types/express@^4.7.0": +"@types/express@^4.17.21": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -2935,9 +2621,9 @@ integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ== "@types/lodash@^4.14.168": - version "4.17.4" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7" - integrity sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ== + version "4.17.7" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.7.tgz#2f776bcb53adc9e13b2c0dfd493dfcbd7de43612" + integrity sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA== "@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.2": version "0.1.4" @@ -2984,9 +2670,9 @@ undici-types "~5.26.4" "@types/node@18": - version "18.19.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.33.tgz#98cd286a1b8a5e11aa06623210240bcc28e95c48" - integrity sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A== + version "18.19.39" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.39.tgz#c316340a5b4adca3aee9dcbf05de385978590593" + integrity sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ== dependencies: undici-types "~5.26.4" @@ -3010,11 +2696,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== -"@types/q@^1.5.1": - version "1.5.8" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837" - integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw== - "@types/qrcode@^1.3.5": version "1.5.5" resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac" @@ -3094,10 +2775,10 @@ resolved "https://registry.yarnpkg.com/@types/sdp-transform/-/sdp-transform-2.4.9.tgz#26ef39f487a6909b0512f580b80920a366b27f52" integrity sha512-bVr+/OoZZy7wrHlNcEAAa6PAgKA4BoXPYVN2EijMC5WnGgQ4ZEuixmKnVs2roiAvr7RhIFVH17QD27cojgIZCg== -"@types/seedrandom@3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.4.tgz#e4a8d0fca0168cacc7dba2af0e4a4ea645d3a190" - integrity sha512-/rWdxeiuZenlawrHU+XV6ZHMTKOqrC2hMfeDfLTIWJhDZP5aVqXRysduYHBbhD7CeJO6FJr/D2uBVXB7GT6v7w== +"@types/seedrandom@3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.8.tgz#61cc8ed88f93a3c31289c295e6df8ca40be42bdf" + integrity sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ== "@types/send@*": version "0.17.4" @@ -3136,10 +2817,10 @@ resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz#832c58e460c9435e4e34bb866e85e9146e12cdbb" integrity sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg== -"@types/uuid@^9.0.2": - version "9.0.8" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" - integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== +"@types/uuid@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" + integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== "@types/yargs-parser@*": version "21.0.3" @@ -3154,74 +2835,74 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^7.0.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz#f87a32e8972b8a60024f2f8f12205e7c8108bc41" - integrity sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q== + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz#b3563927341eca15124a18c6f94215f779f5c02a" + integrity sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.12.0" - "@typescript-eslint/type-utils" "7.12.0" - "@typescript-eslint/utils" "7.12.0" - "@typescript-eslint/visitor-keys" "7.12.0" + "@typescript-eslint/scope-manager" "7.16.0" + "@typescript-eslint/type-utils" "7.16.0" + "@typescript-eslint/utils" "7.16.0" + "@typescript-eslint/visitor-keys" "7.16.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" "@typescript-eslint/parser@^7.0.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.12.0.tgz#8761df3345528b35049353db80010b385719b1c3" - integrity sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ== - dependencies: - "@typescript-eslint/scope-manager" "7.12.0" - "@typescript-eslint/types" "7.12.0" - "@typescript-eslint/typescript-estree" "7.12.0" - "@typescript-eslint/visitor-keys" "7.12.0" + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.16.0.tgz#53fae8112f8c912024aea7b499cf7374487af6d8" + integrity sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw== + dependencies: + "@typescript-eslint/scope-manager" "7.16.0" + "@typescript-eslint/types" "7.16.0" + "@typescript-eslint/typescript-estree" "7.16.0" + "@typescript-eslint/visitor-keys" "7.16.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@7.12.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz#259c014362de72dd34f995efe6bd8dda486adf58" - integrity sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg== +"@typescript-eslint/scope-manager@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz#6927d6451537ce648c6af67a2327378d4cc18462" + integrity sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng== dependencies: - "@typescript-eslint/types" "7.12.0" - "@typescript-eslint/visitor-keys" "7.12.0" + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" -"@typescript-eslint/scope-manager@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz#1dd3e63a4411db356a9d040e75864851b5f2619b" - integrity sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ== +"@typescript-eslint/scope-manager@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz#eb0757af5720c9c53c8010d7a0355ae27e17b7e5" + integrity sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw== dependencies: - "@typescript-eslint/types" "7.9.0" - "@typescript-eslint/visitor-keys" "7.9.0" + "@typescript-eslint/types" "7.16.0" + "@typescript-eslint/visitor-keys" "7.16.0" -"@typescript-eslint/type-utils@7.12.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz#9dfaaa1972952f395ec5be4f5bbfc4d3cdc63908" - integrity sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA== +"@typescript-eslint/type-utils@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz#ec52b1932b8fb44a15a3e20208e0bd49d0b6bd00" + integrity sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg== dependencies: - "@typescript-eslint/typescript-estree" "7.12.0" - "@typescript-eslint/utils" "7.12.0" + "@typescript-eslint/typescript-estree" "7.16.0" + "@typescript-eslint/utils" "7.16.0" debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/types@7.12.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.12.0.tgz#bf208f971a8da1e7524a5d9ae2b5f15192a37981" - integrity sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg== +"@typescript-eslint/types@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.13.0.tgz#0cca95edf1f1fdb0cfe1bb875e121b49617477c5" + integrity sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA== -"@typescript-eslint/types@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.9.0.tgz#b58e485e4bfba055659c7e683ad4f5f0821ae2ec" - integrity sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w== +"@typescript-eslint/types@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.16.0.tgz#60a19d7e7a6b1caa2c06fac860829d162a036ed2" + integrity sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw== -"@typescript-eslint/typescript-estree@7.12.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz#e6c1074f248b3db6573ab6a7c47a39c4cd498ff9" - integrity sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ== +"@typescript-eslint/typescript-estree@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz#4cc24fc155088ebf3b3adbad62c7e60f72c6de1c" + integrity sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw== dependencies: - "@typescript-eslint/types" "7.12.0" - "@typescript-eslint/visitor-keys" "7.12.0" + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -3229,13 +2910,13 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/typescript-estree@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz#3395e27656060dc313a6b406c3a298b729685e07" - integrity sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg== +"@typescript-eslint/typescript-estree@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz#98ac779d526fab2a781e5619c9250f3e33867c09" + integrity sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw== dependencies: - "@typescript-eslint/types" "7.9.0" - "@typescript-eslint/visitor-keys" "7.9.0" + "@typescript-eslint/types" "7.16.0" + "@typescript-eslint/visitor-keys" "7.16.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -3243,40 +2924,40 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.12.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.12.0.tgz#c6e58fd7f724cdccc848f71e388ad80cbdb95dd0" - integrity sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ== +"@typescript-eslint/utils@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.16.0.tgz#b38dc0ce1778e8182e227c98d91d3418449aa17f" + integrity sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.12.0" - "@typescript-eslint/types" "7.12.0" - "@typescript-eslint/typescript-estree" "7.12.0" + "@typescript-eslint/scope-manager" "7.16.0" + "@typescript-eslint/types" "7.16.0" + "@typescript-eslint/typescript-estree" "7.16.0" "@typescript-eslint/utils@^6.0.0 || ^7.0.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.9.0.tgz#1b96a34eefdca1c820cb1bbc2751d848b4540899" - integrity sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA== + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.13.0.tgz#f84e7e8aeceae945a9a3f40d077fd95915308004" + integrity sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.9.0" - "@typescript-eslint/types" "7.9.0" - "@typescript-eslint/typescript-estree" "7.9.0" + "@typescript-eslint/scope-manager" "7.13.0" + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/typescript-estree" "7.13.0" -"@typescript-eslint/visitor-keys@7.12.0": - version "7.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz#c053b55a996679528beeedd8e565710ce1ae1ad3" - integrity sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ== +"@typescript-eslint/visitor-keys@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz#2eb7ce8eb38c2b0d4a494d1fe1908e7071a1a353" + integrity sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw== dependencies: - "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/types" "7.13.0" eslint-visitor-keys "^3.4.3" -"@typescript-eslint/visitor-keys@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz#82162656e339c3def02895f5c8546f6888d9b9ea" - integrity sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ== +"@typescript-eslint/visitor-keys@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz#a1d99fa7a3787962d6e0efd436575ef840e23b06" + integrity sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg== dependencies: - "@typescript-eslint/types" "7.9.0" + "@typescript-eslint/types" "7.16.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": @@ -3284,29 +2965,27 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vector-im/compound-design-tokens@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.2.0.tgz#ccb15fffc24cc70d83593bfc5348e6a0198cc08a" - integrity sha512-8LSbb38KxvStcOQZDSi7lI4oqtCuHFEgEQi9Q0KUx+5OnklfdyJ638txM1bznX/Cp9lHgMk4dHrTiQHBOE0ZuA== - dependencies: - svg2vectordrawable "^2.9.1" +"@vector-im/compound-design-tokens@^1.6.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.6.1.tgz#3f1bb5b2b9f8aff10144aab19dfa11165c3c927b" + integrity sha512-u5xG/8AN7QkPPWhugj0ZrQtWsAjuKHzuOoP0s3bbDg7ZkKTE9l5tM29bdOHnSv9mEYKO+KVMMfsl0W1rlaTmAw== -"@vector-im/compound-web@^4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-4.4.1.tgz#378c6874888becd4b6dd3541904f63300b9ba09a" - integrity sha512-KLYSU8GxR8EBuz+gKSoLLs4+s5xV4stUDbqJu5GG52OmO3YQlvmz/e5/uHYvzfbqBBU5dMmZhz5bdJJ38qxHPQ== +"@vector-im/compound-web@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-5.4.0.tgz#b95262197199c11931a8c6f5269514eb9461f187" + integrity sha512-+EPbr8HzlGEWSePEcPs2iQEBnjXvHGWK177SKF8IO2C7Z2Ygddxa2VTQ7oqtrUfgT+NB5IBTLyXV4Nx7FLgmMA== dependencies: "@floating-ui/react" "^0.26.9" "@floating-ui/react-dom" "^2.0.8" "@radix-ui/react-context-menu" "^2.1.5" "@radix-ui/react-dropdown-menu" "^2.0.6" "@radix-ui/react-form" "^0.0.3" + "@radix-ui/react-progress" "^1.0.3" "@radix-ui/react-separator" "^1.0.3" "@radix-ui/react-slot" "^1.0.2" "@radix-ui/react-tooltip" "^1.0.6" - "@storybook/manager-api" "^8.1.1" classnames "^2.3.2" - graphemer "^1.4.0" + ts-xor "^1.3.0" vaul "^0.7.0" "@zxcvbn-ts/core@^3.0.4": @@ -3331,11 +3010,6 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -abs-svg-path@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf" - integrity sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA== - accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -3492,14 +3166,14 @@ aria-hidden@^1.1.1: dependencies: tslib "^2.0.0" -aria-query@5.1.3: +aria-query@5.1.3, aria-query@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== dependencies: deep-equal "^2.0.5" -aria-query@^5.0.0, aria-query@^5.3.0: +aria-query@^5.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== @@ -3519,7 +3193,19 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-includes@^3.1.6, array-includes@^3.1.7: +array-includes@^3.1.6, array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-includes@^3.1.7: version "3.1.7" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== @@ -3535,7 +3221,7 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.findlast@^1.2.4: +array.prototype.findlast@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== @@ -3588,15 +3274,15 @@ array.prototype.toreversed@^1.1.2: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.tosorted@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" - integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== dependencies: - call-bind "^1.0.5" + call-bind "^1.0.7" define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.1.0" + es-abstract "^1.23.3" + es-errors "^1.3.0" es-shim-unscopables "^1.0.2" arraybuffer.prototype.slice@^1.0.3: @@ -3632,19 +3318,12 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -asynciterator.prototype@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" - integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== - dependencies: - has-symbols "^1.0.3" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -available-typed-arrays@^1.0.5, available-typed-arrays@^1.0.6, available-typed-arrays@^1.0.7: +available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== @@ -3656,22 +3335,17 @@ await-lock@^2.1.0: resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== -axe-core@4.9.1, axe-core@~4.9.1: +axe-core@4.9.1, axe-core@^4.9.1, axe-core@~4.9.1: version "4.9.1" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== -axe-core@=4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" - integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== - -axobject-query@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" - integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== +axobject-query@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" + integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== dependencies: - dequal "^2.0.3" + deep-equal "^2.0.5" babel-jest@^29.0.0, babel-jest@^29.7.0: version "29.7.0" @@ -3767,21 +3441,16 @@ balanced-match@^2.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== -base-x@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" - integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== +base-x@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b" + integrity sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ== base64-arraybuffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== -big-integer@^1.6.48: - version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -3798,9 +3467,9 @@ blob-polyfill@^7.0.0: integrity sha512-oD8Ydw+5lNoqq+en24iuPt1QixdPpe/nUF8azTHnviCZYu9zUC+TwdzIp5orpblJosNlgNbVmmAb//c6d6ImUQ== bloom-filters@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/bloom-filters/-/bloom-filters-3.0.1.tgz#13e28ed22febe2489cd00ba5bd98fdc90e820180" - integrity sha512-rU9IU6bgZ1jmqcLWhlKSidrFjbIGjB89CJBsQqUj1+3/11tAJDwn+f7iRu4bbQ2srTjGgNeoWNwcnelumqdi0g== + version "3.0.2" + resolved "https://registry.yarnpkg.com/bloom-filters/-/bloom-filters-3.0.2.tgz#9c386fca1913da554ededf7a7163bbb93a82d1dd" + integrity sha512-QPKiokjBy16SrBh8T/FAWo74VuNwACnJ9t+q15a+9w5CDaOqHTPPBrDUy70U7YE4+DmENRodtlEdeeq1pB4DZQ== dependencies: base64-arraybuffer "^1.0.2" is-buffer "^2.0.5" @@ -3835,11 +3504,6 @@ body-parser@1.20.2: type-is "~1.6.18" unpipe "1.0.0" -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3862,16 +3526,6 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.21.3: - version "4.21.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== - dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" - browserslist@^4.22.2, browserslist@^4.23.0: version "4.23.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" @@ -3882,12 +3536,12 @@ browserslist@^4.22.2, browserslist@^4.23.0: node-releases "^2.0.14" update-browserslist-db "^1.0.13" -bs58@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" - integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== +bs58@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-6.0.0.tgz#a2cda0130558535dd281a2f8697df79caaf425d8" + integrity sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw== dependencies: - base-x "^4.0.0" + base-x "^5.0.0" bser@2.1.1: version "2.1.1" @@ -3937,11 +3591,6 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001449: - version "1.0.30001486" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz#56a08885228edf62cbe1ac8980f2b5dae159997e" - integrity sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg== - caniuse-lite@^1.0.30001587: version "1.0.30001629" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz#907a36f4669031bd8a1a8dbc2fa08b29e0db297e" @@ -3952,7 +3601,7 @@ chalk@5.2.0: resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== -chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4061,15 +3710,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - collect-v8-coverage@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" @@ -4116,11 +3756,6 @@ commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -4265,25 +3900,6 @@ css-functions-list@^3.2.2: resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.2.tgz#9a54c6dd8416ed25c1079cd88234e927526c1922" integrity sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ== -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - css-tree@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" @@ -4292,11 +3908,6 @@ css-tree@^2.3.1: mdn-data "2.0.30" source-map-js "^1.0.1" -css-what@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - css.escape@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" @@ -4317,13 +3928,6 @@ cssfontparser@^1.2.1: resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" integrity sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg== -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - cssom@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" @@ -4502,7 +4106,7 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -dequal@^2.0.2, dequal@^2.0.3: +dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -4586,15 +4190,6 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - dom-serializer@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" @@ -4604,7 +4199,7 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" -domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: +domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -4616,13 +4211,6 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" -domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" @@ -4630,15 +4218,6 @@ domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - domutils@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" @@ -4663,11 +4242,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.284: - version "1.4.385" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.385.tgz#1afd8d6280d510145148777b899ff481c65531ff" - integrity sha512-L9zlje9bIw0h+CwPQumiuVlfMcV4boxRjFIWDcLfFqTZNbkwOExBzfmswytHawObQX4OUhtNv8gIiB21kOurIg== - electron-to-chromium@^1.4.668: version "1.4.792" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.792.tgz#738712f99d02f70c5754ca4264782915fa946849" @@ -4693,10 +4267,10 @@ emojibase-data@^15.0.0: resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-15.2.0.tgz#475a786c091a101ef4bcf57227771c6260ee39b2" integrity sha512-hDiw4ugxnI4pcVQO+73NlKx6aZP/A+BAPfDgK/3A83RVbHZa0Ut6GHpd5r5XUV9G7BZhKejlIRuxhXialpbt6Q== -emojibase-regex@15.3.0: - version "15.3.0" - resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-15.3.0.tgz#98c9683a481ccb1fe6aefddb495b2d692bbf5368" - integrity sha512-EBz/292VBF9naBPBsGzkZUccgIv1xJibTXIINl8SezgVRnTCpKJx7MgZcR+UAd2RwjGkRJJZ/lhP7riOFZLicA== +emojibase-regex@15.3.2: + version "15.3.2" + resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-15.3.2.tgz#5175231715b86d4b437754527288844a6c29318f" + integrity sha512-ue6BVeb2qu33l97MkxcOoyMJlg6Tug3eTv2z1at+M9TjvlWKvdmAPvZIDG1JbT2RH3FSyJNLucO5K5H/yxT03w== emojibase@^15.0.0: version "15.2.0" @@ -4718,11 +4292,6 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - entities@^4.2.0: version "4.4.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" @@ -4743,151 +4312,17 @@ env-paths@^2.2.1: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.17.5: - version "1.22.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.4.tgz#26eb2e7538c3271141f5754d31aabfdb215f27bf" - integrity sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.6" - call-bind "^1.0.7" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.2" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.1" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.0" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.8" - string.prototype.trimend "^1.0.7" - string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.1" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.14" - -es-abstract@^1.18.3: - version "1.21.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" - integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== - dependencies: - array-buffer-byte-length "^1.0.0" - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.0" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.10" - is-weakref "^1.0.2" - object-inspect "^1.12.3" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" - -es-abstract@^1.22.1, es-abstract@^1.22.3: - version "1.22.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.5.tgz#1417df4e97cc55f09bf7e58d1e614bc61cb8df46" - integrity sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w== +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.1" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.0" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.8" - string.prototype.trimend "^1.0.7" - string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.5" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.14" + is-arrayish "^0.2.1" -es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: - version "1.23.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" - integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== +es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== dependencies: array-buffer-byte-length "^1.0.1" arraybuffer.prototype.slice "^1.0.3" @@ -4928,11 +4363,11 @@ es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: safe-regex-test "^1.0.3" string.prototype.trim "^1.2.9" string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.7" + string.prototype.trimstart "^1.0.8" typed-array-buffer "^1.0.2" typed-array-byte-length "^1.0.1" typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.5" + typed-array-length "^1.0.6" unbox-primitive "^1.0.2" which-typed-array "^1.1.15" @@ -4943,7 +4378,7 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" -es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== @@ -4963,34 +4398,14 @@ es-get-iterator@^1.1.3: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-iterator-helpers@^1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" - integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== - dependencies: - asynciterator.prototype "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.1" - es-abstract "^1.22.1" - es-set-tostringtag "^2.0.1" - function-bind "^1.1.1" - get-intrinsic "^1.2.1" - globalthis "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.5" - iterator.prototype "^1.1.2" - safe-array-concat "^1.0.1" - -es-iterator-helpers@^1.0.17: - version "1.0.18" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" - integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== +es-iterator-helpers@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" + integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== dependencies: call-bind "^1.0.7" define-properties "^1.2.1" - es-abstract "^1.23.0" + es-abstract "^1.23.3" es-errors "^1.3.0" es-set-tostringtag "^2.0.3" function-bind "^1.1.2" @@ -5010,7 +4425,7 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.0.1, es-set-tostringtag@^2.0.2, es-set-tostringtag@^2.0.3: +es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== @@ -5126,33 +4541,33 @@ eslint-plugin-import@^2.25.4: tsconfig-paths "^3.15.0" eslint-plugin-jest@^28.0.0: - version "28.5.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.5.0.tgz#b497b795de37f671eaccd38bd83030186ff5dc8d" - integrity sha512-6np6DGdmNq/eBbA7HOUNV8fkfL86PYwBfwyb8n23FXgJNTR8+ot3smRHjza9LGsBBZRypK3qyF79vMjohIL8eQ== + version "28.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz#8410588d60bcafa68a91b6ec272e4a415502302a" + integrity sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg== dependencies: "@typescript-eslint/utils" "^6.0.0 || ^7.0.0" eslint-plugin-jsx-a11y@^6.5.1: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz#2fa9c701d44fcd722b7c771ec322432857fcbad2" - integrity sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA== + version "6.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz#67ab8ff460d4d3d6a0b4a570e9c1670a0a8245c8" + integrity sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g== dependencies: - "@babel/runtime" "^7.23.2" - aria-query "^5.3.0" - array-includes "^3.1.7" + aria-query "~5.1.3" + array-includes "^3.1.8" array.prototype.flatmap "^1.3.2" ast-types-flow "^0.0.8" - axe-core "=4.7.0" - axobject-query "^3.2.1" + axe-core "^4.9.1" + axobject-query "~3.1.1" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" - es-iterator-helpers "^1.0.15" - hasown "^2.0.0" + es-iterator-helpers "^1.0.19" + hasown "^2.0.2" jsx-ast-utils "^3.3.5" language-tags "^1.0.9" minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.0" eslint-plugin-matrix-org@1.2.1: version "1.2.1" @@ -5165,33 +4580,33 @@ eslint-plugin-react-hooks@^4.3.0: integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== eslint-plugin-react@^7.28.0: - version "7.34.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" - integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== + version "7.34.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz#9965f27bd1250a787b5d4cfcc765e5a5d58dcb7b" + integrity sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA== dependencies: - array-includes "^3.1.7" - array.prototype.findlast "^1.2.4" + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" array.prototype.flatmap "^1.3.2" array.prototype.toreversed "^1.1.2" - array.prototype.tosorted "^1.1.3" + array.prototype.tosorted "^1.1.4" doctrine "^2.1.0" - es-iterator-helpers "^1.0.17" + es-iterator-helpers "^1.0.19" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" - object.hasown "^1.1.3" - object.values "^1.1.7" + object.entries "^1.1.8" + object.fromentries "^2.0.8" + object.hasown "^1.1.4" + object.values "^1.2.0" prop-types "^15.8.1" resolve "^2.0.0-next.5" semver "^6.3.1" - string.prototype.matchall "^4.0.10" + string.prototype.matchall "^4.0.11" -eslint-plugin-unicorn@^53.0.0: - version "53.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz#df3a5c9ecabeb759e6fd867b2d84198466ac8c4d" - integrity sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw== +eslint-plugin-unicorn@^54.0.0: + version "54.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-54.0.0.tgz#ce3ea853e8fd7ca2bda2fd6065bf065adb5d8b6d" + integrity sha512-XxYLRiYtAWiAjPv6z4JREby1TAE2byBC7wlh0V4vWDCpccOSU1KovWV//jqPXF6bq3WKxqX9rdjoRQ1EhdmNdQ== dependencies: "@babel/helper-validator-identifier" "^7.24.5" "@eslint-community/eslint-utils" "^4.4.0" @@ -5432,10 +4847,10 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -fake-indexeddb@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-5.0.2.tgz#8e0b6c75c6dc6639cbb50c1aa948772147d7c93e" - integrity sha512-cB507r5T3D55DfclY01GLkninZLfU7HXV/mhVRTnTRm5k2u+fY7Fof2dBkr80p5t7G7dlA/G5dI87QiMdPpMCQ== +fake-indexeddb@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz#3173d5ad141436dace95f8de6e9ecdc3d9787d5d" + integrity sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -5529,18 +4944,10 @@ file-saver@^2.0.5: resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== -file-system-cache@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-2.3.0.tgz#201feaf4c8cd97b9d0d608e96861bb6005f46fe6" - integrity sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ== - dependencies: - fs-extra "11.1.1" - ramda "0.29.0" - -filesize@10.1.2: - version "10.1.2" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.2.tgz#33bb71c5c134102499f1bc36e6f2863137f6cb0c" - integrity sha512-Dx770ai81ohflojxhU+oG+Z2QGvKdYxgEr9OSA8UVrqhwNHjfH9A8f5NKfg83fEH8ZFA5N5llJo5T3PIoZ4CRA== +filesize@10.1.4: + version "10.1.4" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.4.tgz#184f256063a201f08b6e6b3cc47d21b60f5b8d89" + integrity sha512-ryBwPIIeErmxgPnm6cbESAzXjuEFubs+yKYLBZvg3CaiNcmkJChoOGcBSrZ6IwkMwPABwPpVXE6IlNdGJJrvEg== fill-range@^7.1.1: version "7.1.1" @@ -5640,9 +5047,9 @@ foreachasync@^3.0.0: integrity sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw== foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== dependencies: cross-spawn "^7.0.0" signal-exit "^4.0.1" @@ -5666,15 +5073,6 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -fs-extra@11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" - integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-extra@^11.0.0: version "11.2.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" @@ -5704,7 +5102,7 @@ fsevents@^2.3.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1, function-bind@^1.1.2: +function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== @@ -5739,7 +5137,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -5765,7 +5163,7 @@ get-stream@^6.0.0, get-stream@^6.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -get-symbol-description@^1.0.0, get-symbol-description@^1.0.2: +get-symbol-description@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== @@ -5774,10 +5172,10 @@ get-symbol-description@^1.0.0, get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" -gfm.css@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/gfm.css/-/gfm.css-1.1.2.tgz#94acfa600672663b9dd0fd4b6ee5d11c8dbc161e" - integrity sha512-KhK3rqxMj+UTLRxWnfUA5n8XZYMWfHrrcCxtWResYR2B3hWIqBM6v9FPGZSlVuX+ScLewizOvNkjYXuPs95ThQ== +github-markdown-css@^5.5.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/github-markdown-css/-/github-markdown-css-5.6.1.tgz#8ca3d5c3d93d79ea429fddafea091347ab374f78" + integrity sha512-DItLFgHd+s7HQmk63YN4/TdvLeRqk1QP7pPKTTPrDTYoI5x7f/luJWSOZxesmuxBI2srHp8RDyoZd+9WF+WK8Q== gl-matrix@^3.4.3: version "3.4.3" @@ -5803,16 +5201,17 @@ glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.3.7: - version "10.3.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.15.tgz#e72bc61bc3038c90605f5dd48543dc67aaf3b50d" - integrity sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw== +glob@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e" + integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g== dependencies: foreground-child "^3.1.0" - jackspeak "^2.3.6" - minimatch "^9.0.1" - minipass "^7.0.4" - path-scurry "^1.11.0" + jackspeak "^4.0.1" + minimatch "^10.0.0" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" @@ -5860,11 +5259,12 @@ globals@^14.0.0: integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== dependencies: - define-properties "^1.1.3" + define-properties "^1.2.1" + gopd "^1.0.1" globby@^11.1.0: version "11.1.0" @@ -5932,7 +5332,7 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-tostringtag@^1.0.0, has-tostringtag@^1.0.1, has-tostringtag@^1.0.2: +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== @@ -5952,9 +5352,9 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: function-bind "^1.1.2" highlight.js@^11.3.1: - version "11.9.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" - integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== + version "11.10.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92" + integrity sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ== hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" @@ -6111,16 +5511,7 @@ ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -internal-slot@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" - integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== - dependencies: - get-intrinsic "^1.2.2" - hasown "^2.0.0" - side-channel "^1.0.4" - -internal-slot@^1.0.5, internal-slot@^1.0.7: +internal-slot@^1.0.4, internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== @@ -6225,7 +5616,14 @@ is-core-module@^2.11.0: dependencies: has "^1.0.3" -is-core-module@^2.13.0, is-core-module@^2.13.1: +is-core-module@^2.13.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" + integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== + dependencies: + hasown "^2.0.2" + +is-core-module@^2.13.1: version "2.13.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== @@ -6289,17 +5687,12 @@ is-ip@^3.1.0: dependencies: ip-regex "^4.0.0" -is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== - -is-map@^2.0.3: +is-map@^2.0.2, is-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== -is-negative-zero@^2.0.2, is-negative-zero@^2.0.3: +is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== @@ -6346,12 +5739,7 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== - -is-set@^2.0.3: +is-set@^2.0.2, is-set@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== @@ -6380,11 +5768,6 @@ is-subset@^0.1.1: resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" integrity sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw== -is-svg-path@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-svg-path/-/is-svg-path-1.0.2.tgz#77ab590c12b3d20348e5c7a13d0040c87784dda0" - integrity sha512-Lj4vePmqpPR1ZnRctHv8ltSh1OrSxHkhUkd7wi+VQdcdP15/KvQFyk7LhNuM7ZW0EVbJz8kZLVmL9quLrfq4Kg== - is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" @@ -6392,7 +5775,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.13: +is-typed-array@^1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== @@ -6503,10 +5886,10 @@ iterator.prototype@^1.1.2: reflect.getprototypeof "^1.0.4" set-function-name "^2.0.1" -jackspeak@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== +jackspeak@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.1.tgz#9fca4ce961af6083e259c376e9e3541431f5287b" + integrity sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -6902,10 +6285,10 @@ jest@^29.6.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-xxhash@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/js-xxhash/-/js-xxhash-3.0.1.tgz#e093b53d02cd80a830d61f58290c206aaa877b24" - integrity sha512-Y2NSC77RIxJrvi2NoXjMi2LYsVDTlVqBoQRi8PXQg4PtP29wdtIOhsp8Ujw4EjEkBFheCPx8bMOmI9zoxx/3jQ== +js-xxhash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-xxhash/-/js-xxhash-4.0.0.tgz#866b003c06ba39bebc9e4a47d2756abfef06606c" + integrity sha512-3Q2eIqG6s1KEBBmkj9tGM9lef8LJbuRyTVBdI3GpTnrvtytunjLPO0wqABp5qhtMzfA32jYn1FlnIV7GH1RAHQ== js-yaml@^3.13.1: version "3.14.1" @@ -7046,9 +6429,9 @@ jwt-decode@4.0.0, jwt-decode@^4.0.0: integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== katex@^0.16.0: - version "0.16.10" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.10.tgz#6f81b71ac37ff4ec7556861160f53bc5f058b185" - integrity sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA== + version "0.16.11" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.11.tgz#4bc84d5584f996abece5f01c6ad11304276a33f5" + integrity sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ== dependencies: commander "^8.3.0" @@ -7074,20 +6457,15 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -known-css-properties@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.30.0.tgz#34dd1f39c805c65a6dfa6ea76206b20dc523dd96" - integrity sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ== - known-css-properties@^0.31.0: version "0.31.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.31.0.tgz#5c8d9d8777b3ca09482b2397f6a241e5d69a1023" integrity sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ== language-subtag-registry@^0.3.20: - version "0.3.22" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" - integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== + version "0.3.23" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== language-tags@^1.0.9: version "1.0.9" @@ -7109,13 +6487,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lie@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== - dependencies: - immediate "~3.0.5" - lie@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -7157,13 +6528,6 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" -localforage@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" - integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== - dependencies: - lie "3.1.1" - locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -7243,10 +6607,10 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" - integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== +lru-cache@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.0.tgz#15d93a196f189034d7166caf9fe55e7384c98a21" + integrity sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA== lru-cache@^5.1.1: version "5.1.1" @@ -7301,11 +6665,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-or-similar@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" - integrity sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg== - maplibre-gl@^2.0.0: version "2.4.0" resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-2.4.0.tgz#2b53dbf526626bf4ee92ad4f33f13ef09e5af182" @@ -7351,15 +6710,16 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@33.1.0: - version "33.1.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-33.1.0.tgz#e6fe3a521955bb2e2b200a5ca2be1a1ea76a8c90" - integrity sha512-Spf+g156eK+SqPd7EuulTVwpyHkVXR0Ikme04fMCKer+SbzuLPPnqV3gL5cSyTMmHbdNejEJaEvzNIMQoD7Xxw== +matrix-js-sdk@34.2.0: + version "34.2.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.2.0.tgz#5e7eff9b4c15689d7f07ad3686373f821e2f06bf" + integrity sha512-dygfH/a0C/Q+a5dSfudxxwA0g9peLsBbalC6LaxPa7AEFb4Gg9d8kiGnlqaFb1U9bGUapk8duBsAC526BjXbdA== dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^5.0.0" + "@matrix-org/matrix-sdk-crypto-wasm" "^6.0.0" + "@matrix-org/olm" "3.2.15" another-json "^0.2.0" - bs58 "^5.0.0" + bs58 "^6.0.0" content-type "^1.0.4" jwt-decode "^4.0.0" loglevel "^1.7.1" @@ -7369,12 +6729,12 @@ matrix-js-sdk@33.1.0: p-retry "4" sdp-transform "^2.14.1" unhomoglyph "^1.0.6" - uuid "9" + uuid "10" matrix-web-i18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/matrix-web-i18n/-/matrix-web-i18n-3.2.1.tgz#34e6b66bec71a52fddbe72db56d9e35dabbaff59" - integrity sha512-pBklE6Q6mAwG6N3Qtpu/e+qX0XuWEdrs4SZ+QmYJWfyLNtKAB6XcSpE5m7aBW/+11ejg8ua8Q5bNcDV2b7C9lg== + version "3.3.0" + resolved "https://registry.yarnpkg.com/matrix-web-i18n/-/matrix-web-i18n-3.3.0.tgz#a9f9d87d18ef96f75171883abbf201952cbfbe22" + integrity sha512-bJPJrBGrCdslkf2wMVHWyZlAEx9zSKnOsJ9rILaaEy195yyNLpXrYoyRIXEk8YWsdwtaK1ImE+r/Gh43J/I4ow== dependencies: "@babel/parser" "^7.18.5" "@babel/traverse" "^7.18.5" @@ -7399,11 +6759,6 @@ md5@^2.3.0: crypt "0.0.2" is-buffer "~1.1.6" -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - mdn-data@2.0.30: version "2.0.30" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" @@ -7429,13 +6784,6 @@ memoize-one@^6.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== -memoizerific@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" - integrity sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog== - dependencies: - map-or-similar "^1.5.0" - meow@^13.2.0: version "13.2.0" resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" @@ -7496,6 +6844,13 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +minimatch@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" + integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== + dependencies: + brace-expansion "^2.0.1" + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -7503,10 +6858,10 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^9.0.1, minimatch@^9.0.4: - version "9.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" - integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" @@ -7515,12 +6870,12 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8, minimist@~1. resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4: - version "7.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.1.tgz#f7f85aff59aa22f110b20e27692465cf3bf89481" - integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA== +minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -mkdirp@1.0.4, mkdirp@^1.0.4: +mkdirp@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -7605,11 +6960,6 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== - normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -7625,13 +6975,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-svg-path@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz#0e614eca23c39f0cffe821d6be6cd17e569a766c" - integrity sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg== - dependencies: - svg-arc-to-cubic-bezier "^3.0.0" - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -7639,13 +6982,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - nwsapi@^2.2.2: version "2.2.7" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" @@ -7656,18 +6992,18 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.12.3, object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== object-is@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" + call-bind "^1.0.7" + define-properties "^1.2.1" object-keys@^1.1.1: version "1.1.1" @@ -7684,7 +7020,7 @@ object.assign@^4.1.4, object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.7: +object.entries@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== @@ -7693,7 +7029,7 @@ object.entries@^1.1.7: define-properties "^1.2.1" es-object-atoms "^1.0.0" -object.fromentries@^2.0.7: +object.fromentries@^2.0.7, object.fromentries@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== @@ -7713,15 +7049,16 @@ object.groupby@^1.0.1: es-abstract "^1.22.1" get-intrinsic "^1.2.1" -object.hasown@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" - integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== +object.hasown@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" + integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== dependencies: - define-properties "^1.2.0" - es-abstract "^1.22.1" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" -object.values@^1.1.6, object.values@^1.1.7: +object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== @@ -7823,6 +7160,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + pako@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" @@ -7855,11 +7197,6 @@ parse-srcset@^1.0.2: resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== -parse-svg-path@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb" - integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ== - parse5@^7.0.0, parse5@^7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" @@ -7897,13 +7234,13 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.11.0: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== +path-scurry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" + integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + lru-cache "^11.0.0" + minipass "^7.1.2" path-to-regexp@0.1.7: version "0.1.7" @@ -7962,17 +7299,17 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -playwright-core@1.44.1: - version "1.44.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c" - integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA== +playwright-core@1.45.2, playwright-core@^1.45.1: + version "1.45.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.45.2.tgz#c8b8b7f66eda47fb2bd24e5435c92d1163022df8" + integrity sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw== -playwright@1.44.1: - version "1.44.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892" - integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg== +playwright@1.45.2: + version "1.45.2" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.45.2.tgz#21082072120a2c8a7e3bbb2792e81e8aa367b7a7" + integrity sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g== dependencies: - playwright-core "1.44.1" + playwright-core "1.45.2" optionalDependencies: fsevents "2.3.2" @@ -8023,14 +7360,6 @@ postcss-scss@^4.0.4: resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== -postcss-selector-parser@^6.0.15: - version "6.0.16" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" - integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - postcss-selector-parser@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" @@ -8062,13 +7391,14 @@ postcss@^8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" -posthog-js@1.135.2: - version "1.135.2" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.135.2.tgz#1da1508760521e6f0fe1ab908bc4ffbe04c2952c" - integrity sha512-kqix067CyrlcNKUhVxrys8Qp0O/8FUtlkp7lfM+tkJFJAMZsKjIDVslz2AjI9y79CvyyZX+pddfA7F3YFYlS0Q== +posthog-js@1.145.0: + version "1.145.0" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.145.0.tgz#5159459f02988b74407a1dd2b19469c422b31feb" + integrity sha512-LQdH6S2Ks3mnCI0q9aD5SZS0Uujc/90nuJuEeGDeGkWkVkYOSQJt4n0UHrIWEsZdmIKZf0a6OIBhTmO+yUiY3w== dependencies: fflate "^0.4.8" preact "^10.19.3" + web-vitals "^4.0.1" potpack@^1.0.2: version "1.0.2" @@ -8085,10 +7415,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" - integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== +prettier@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" + integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== pretty-format@^27.0.2: version "27.5.1" @@ -8130,14 +7460,6 @@ prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -proposal-temporal@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/proposal-temporal/-/proposal-temporal-0.9.0.tgz#4841cf83cf270f85a829e9283843ea8796d3d86f" - integrity sha512-AyNg3NmmBDCDbABQDmsnsY1B8VciwO9wZm+C3rClAgkPre+SpZDcIGje0WLZwroyqUFDySqW7VV6vcvAv8Bi+Q== - dependencies: - big-integer "^1.6.48" - es-abstract "^1.18.3" - protocol-buffers-schema@^3.3.1: version "3.6.0" resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" @@ -8183,11 +7505,6 @@ pvutils@^1.1.3: resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3" integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ== -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== - qrcode@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" @@ -8205,13 +7522,6 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.10.0: - version "6.12.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a" - integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ== - dependencies: - side-channel "^1.0.6" - querystring@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" @@ -8237,11 +7547,6 @@ raf-schd@^4.0.2: resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== -ramda@0.29.0: - version "0.29.0" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.29.0.tgz#fbbb67a740a754c8a4cbb41e2a6e0eb8507f55fb" - integrity sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA== - range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -8350,7 +7655,7 @@ react-redux@^7.2.0: prop-types "^15.7.2" react-is "^17.0.2" -react-remove-scroll-bar@^2.3.3: +react-remove-scroll-bar@^2.3.4: version "2.3.6" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== @@ -8358,12 +7663,12 @@ react-remove-scroll-bar@^2.3.3: react-style-singleton "^2.2.1" tslib "^2.0.0" -react-remove-scroll@2.5.5: - version "2.5.5" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" - integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw== +react-remove-scroll@2.5.7: + version "2.5.7" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb" + integrity sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA== dependencies: - react-remove-scroll-bar "^2.3.3" + react-remove-scroll-bar "^2.3.4" react-style-singleton "^2.2.1" tslib "^2.1.0" use-callback-ref "^1.3.0" @@ -8497,16 +7802,7 @@ regexp-tree@^0.1.27: resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== -regexp.prototype.flags@^1.4.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" - integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - set-function-name "^2.0.0" - -regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: +regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== @@ -8649,12 +7945,12 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^5.0.0: - version "5.0.7" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.7.tgz#27bddf202e7d89cb2e0381656380d1734a854a74" - integrity sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg== +rimraf@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.0.tgz#503bb3d9283272384c121792d40e7ee3ab763cde" + integrity sha512-u+yqhM92LW+89cxUQK0SRyvXYQmyuKHx0jkx4W7KfwLGLqJnQM5031Uv1trE4gB9XEXBM/s6MxKlfW95IidqaA== dependencies: - glob "^10.3.7" + glob "^11.0.0" run-parallel@^1.1.9: version "1.2.0" @@ -8663,17 +7959,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-array-concat@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.0.tgz#8d0cae9cb806d6d1c06e08ab13d847293ebe0692" - integrity sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg== - dependencies: - call-bind "^1.0.5" - get-intrinsic "^1.2.2" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-array-concat@^1.1.0, safe-array-concat@^1.1.2: +safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== @@ -8693,7 +7979,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex-test@^1.0.0, safe-regex-test@^1.0.3: +safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== @@ -8835,7 +8121,7 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" -set-function-name@^2.0.0, set-function-name@^2.0.1: +set-function-name@^2.0.1, set-function-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== @@ -8985,11 +8271,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -9009,11 +8290,6 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" -store2@^2.14.2: - version "2.14.3" - resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.3.tgz#24077d7ba110711864e4f691d2af941ec533deb5" - integrity sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg== - string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -9049,20 +8325,31 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.matchall@^4.0.10: - version "4.0.10" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" - integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== +string.prototype.includes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz#8986d57aee66d5460c144620a6d873778ad7289f" + integrity sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.matchall@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.5" - regexp.prototype.flags "^1.5.0" - set-function-name "^2.0.0" - side-channel "^1.0.4" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" string.prototype.repeat@^1.0.0: version "1.0.0" @@ -9072,16 +8359,7 @@ string.prototype.repeat@^1.0.0: define-properties "^1.1.3" es-abstract "^1.17.5" -string.prototype.trim@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" - integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9: +string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== @@ -9091,16 +8369,7 @@ string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9: es-abstract "^1.23.0" es-object-atoms "^1.0.0" -string.prototype.trimend@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" - integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: +string.prototype.trimend@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== @@ -9109,14 +8378,14 @@ string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string.prototype.trimstart@^1.0.6, string.prototype.trimstart@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" - integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string_decoder@~1.1.1: version "1.1.1" @@ -9173,27 +8442,27 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -stylelint-config-recommended@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-14.0.0.tgz#b395c7014838d2aaca1755eebd914d0bb5274994" - integrity sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ== +stylelint-config-recommended@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz#d25e86409aaf79ee6c6085c2c14b33c7e23c90c6" + integrity sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg== stylelint-config-standard@^36.0.0: - version "36.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-36.0.0.tgz#6704c044d611edc12692d4a5e37b039a441604d4" - integrity sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug== + version "36.0.1" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz#727cbb2a1ef3e210f5ce8329cde531129f156609" + integrity sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw== dependencies: - stylelint-config-recommended "^14.0.0" + stylelint-config-recommended "^14.0.1" stylelint-scss@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.3.0.tgz#2020d0f0d21b8b4151f523e303ae8966728a6e54" - integrity sha512-8OSpiuf1xC7f8kllJsBOFAOYp/mR/C1FXMVeOFjtJPw+AFvEmC93FaklHt7MlOqU4poxuQ1TkYMyfI0V+1SxjA== + version "6.3.2" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.3.2.tgz#085072e774e5a31e65aa2acefaad5417a29d6ec1" + integrity sha512-pNk9mXOVKkQtd+SROPC9io8ISSgX+tOVPhFdBE+LaKQnJMLdWPbGKAGYv4Wmf/RrnOjkutunNTN9kKMhkdE5qA== dependencies: - known-css-properties "^0.30.0" + known-css-properties "^0.31.0" postcss-media-query-parser "^0.2.3" postcss-resolve-nested-selector "^0.1.1" - postcss-selector-parser "^6.0.15" + postcss-selector-parser "^6.1.0" postcss-value-parser "^4.2.0" stylelint@^16.1.0: @@ -9282,55 +8551,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svg-arc-to-cubic-bezier@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6" - integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g== - -svg-path-bounds@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz#00312f672b08afc432a66ddfbd06db40cec8d0d0" - integrity sha512-H4/uAgLWrppIC0kHsb2/dWUYSmb4GE5UqH06uqWBcg6LBjX2fu0A8+JrO2/FJPZiSsNOKZAhyFFgsLTdYUvSqQ== - dependencies: - abs-svg-path "^0.1.1" - is-svg-path "^1.0.1" - normalize-svg-path "^1.0.0" - parse-svg-path "^0.1.2" - svg-tags@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== -svg2vectordrawable@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/svg2vectordrawable/-/svg2vectordrawable-2.9.1.tgz#23186ff7ace7038d09c031176dbca04063a97e5d" - integrity sha512-7WJIh4SzZLyEJtn45y+f8rREkgBiQMWfb0FoYkXuioywESjDWfbSuP0FQEmIiHP2zOi0oOO8pTG4VkeWJyidWw== - dependencies: - coa "^2.0.2" - mkdirp "^1.0.4" - svg-path-bounds "^1.0.1" - svgo "^2.8.0" - svgpath "^2.5.0" - -svgo@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - -svgpath@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/svgpath/-/svgpath-2.6.0.tgz#5b160ef3d742b7dfd2d721bf90588d3450d7a90d" - integrity sha512-OIWR6bKzXvdXYyO4DK/UWa1VA1JeKq8E+0ug2DG98Y/vOmMpfZNj+TIG988HjfYSqtcy/hFOtZq/n/j5GSESNg== - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -9357,12 +8582,17 @@ tar-js@^0.3.0: resolved "https://registry.yarnpkg.com/tar-js/-/tar-js-0.3.0.tgz#6949aabfb0ba18bb1562ae51a439fd0f30183a17" integrity sha512-9uqP2hJUZNKRkwPDe5nXxXdzo6w+BFBPq9x/tyi5/U/DneuSesO/HMb0y5TeWpfcv49YDJTs7SrrZeeu8ZHWDA== -telejson@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/telejson/-/telejson-7.2.0.tgz#3994f6c9a8f8d7f2dba9be2c7c5bbb447e876f32" - integrity sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ== +temporal-polyfill@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/temporal-polyfill/-/temporal-polyfill-0.2.5.tgz#0796c40a50754c69ec0f9a2db3f6c582b9721aaf" + integrity sha512-ye47xp8Cb0nDguAhrrDS1JT1SzwEV9e26sSsrWzVu+yPZ7LzceEcH0i2gci9jWfOfSCCgM3Qv5nOYShVUUFUXA== dependencies: - memoizerific "^1.11.3" + temporal-spec "^0.2.4" + +temporal-spec@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/temporal-spec/-/temporal-spec-0.2.4.tgz#7eb10447a62429ffaaa80b42b869b138ae306a75" + integrity sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ== test-exclude@^6.0.0: version "6.0.0" @@ -9383,11 +8613,6 @@ tiny-invariant@^1.0.6: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== -tiny-invariant@^1.3.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" - integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== - tinyqueue@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" @@ -9456,11 +8681,6 @@ ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -ts-dedent@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" - integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== - ts-node@^10.9.1: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" @@ -9480,6 +8700,11 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +ts-xor@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-xor/-/ts-xor-1.3.0.tgz#3e59f24f0321f9f10f350e0cee3b534b89a2c70b" + integrity sha512-RLXVjliCzc1gfKQFLRpfeD0rrWmjnSTgj7+RFhoq3KRkUYa8LE/TIidYOzM5h+IdFBDSjjSgk9Lto9sdMfDFEA== + tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" @@ -9490,7 +8715,12 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.1, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.1, tslib@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + +tslib@^2.0.3: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -9527,11 +8757,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -9540,7 +8765,7 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typed-array-buffer@^1.0.1, typed-array-buffer@^1.0.2: +typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== @@ -9549,7 +8774,7 @@ typed-array-buffer@^1.0.1, typed-array-buffer@^1.0.2: es-errors "^1.3.0" is-typed-array "^1.1.13" -typed-array-byte-length@^1.0.0, typed-array-byte-length@^1.0.1: +typed-array-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== @@ -9560,7 +8785,7 @@ typed-array-byte-length@^1.0.0, typed-array-byte-length@^1.0.1: has-proto "^1.0.3" is-typed-array "^1.1.13" -typed-array-byte-offset@^1.0.0, typed-array-byte-offset@^1.0.2: +typed-array-byte-offset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== @@ -9572,10 +8797,10 @@ typed-array-byte-offset@^1.0.0, typed-array-byte-offset@^1.0.2: has-proto "^1.0.3" is-typed-array "^1.1.13" -typed-array-length@^1.0.4, typed-array-length@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.5.tgz#57d44da160296d8663fd63180a1802ebf25905d5" - integrity sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA== +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== dependencies: call-bind "^1.0.7" for-each "^0.3.3" @@ -9584,15 +8809,15 @@ typed-array-length@^1.0.4, typed-array-length@^1.0.5: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" -typescript@5.4.5: - version "5.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" - integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== ua-parser-js@^1.0.2: - version "1.0.37" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" - integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== + version "1.0.38" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2" + integrity sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ== unbox-primitive@^1.0.2: version "1.0.2" @@ -9652,14 +8877,6 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.10: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - update-browserslist-db@^1.0.13: version "1.0.16" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" @@ -9718,16 +8935,16 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@10, uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + uuid@8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@9, uuid@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -9792,10 +9009,20 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -webcrypto-core@^1.7.9: - version "1.7.9" - resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.9.tgz#a585f0032dbc88d202cff4f266cbef02ba48bd7a" - integrity sha512-FE+a4PPkOmBbgNDIyRmcHhgXn+2ClRl3JzJdDu/P4+B8y81LqKe6RAsI9b3lAOHe1T1BMkSjsRHTYRikImZnVA== +web-streams-polyfill@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0.tgz#74cedf168339ee6e709532f76c49313a8c7acdac" + integrity sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw== + +web-vitals@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.0.tgz#008949ab79717a68ccaaa3c4371cbc7bbbd78a92" + integrity sha512-ohj72kbtVWCpKYMxcbJ+xaOBV3En76hW47j52dG+tEGG36LZQgfFw5yHl9xyjmosy3XUMn8d/GBUAy4YPM839w== + +webcrypto-core@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.8.0.tgz#aaea17f3dd9c77c304e3c494eb27ca07cc72ca37" + integrity sha512-kR1UQNH8MD42CYuLzvibfakG5Ew5seG85dMMoAM/1LqvckxaF6pUiidLuraIu4V+YCIFabYecUZAW0TuxAoaqw== dependencies: "@peculiar/asn1-schema" "^2.3.8" "@peculiar/json-schema" "^1.1.12" @@ -9904,18 +9131,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== -which-typed-array@^1.1.13: - version "1.1.14" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.14.tgz#1f78a111aee1e131ca66164d8bdc3ab062c95a06" - integrity sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg== - dependencies: - available-typed-arrays "^1.0.6" - call-bind "^1.0.5" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.1" - -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: +which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: version "1.1.15" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==

^EcWkbuSHXQ-SR5|3N}$wV@Hp%dJ5&E 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 BrOf~U literal 0 HcmV?d00001 diff --git a/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png b/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png index 98c1ff245d3785068989c9167dc68f85cfd606bb..2c6160f2a19014674bb21d2a27968750d282a2e6 100644 GIT binary patch literal 25768 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 diff --git a/playwright/snapshots/editing/editing.spec.ts/message-edit-history-dialog-linux.png b/playwright/snapshots/editing/editing.spec.ts/message-edit-history-dialog-linux.png index 9f46fce516bd8ac7c168db853bcbef5bfb676172..bfbfccbaeba24ac1d62fd7060108b1834d576002 100644 GIT binary patch literal 7313 zcmb7JWmr_*+8#mykx&%mC5Mm(6#?n)7LW!dr6qKgTR*E+vIm1q;$%*J4Pls4HVzaa&SZWPOV#Hn(&k@k*CII7^(=E8Uwm-m<~X7s znYyuGmqkmlpQ@wrO#BHjt2b9cF_fU=VL>?gW1#3xnw_n|mcZ*k0~8KNBja6(N$@Fw zXCY?G>dKGADEABQprU4Bsm1&XLIDb^(J_YO*qHbf&?Tq+t;vRi#Hu9-LI`Nl``F)D zi`kkf7?Dv>a%b=$DaC0rqy*IEp^_|P?n4Qkfss(39 z5eyIa5f;`BI~NxW3RO^1G4xqGcOkdl-rjB-EG#OzQAiFB40SzU{-g$}%FHxK6L^Vx zR$XDDU|OcJT9fgdkdO??OH6j+WP3!d$*HX(Yd=3dicK`Iv)I(|rDAvY$_;rzv)HJq zrw4_>S{#Dl1H|K15S%S+78Vn`Ko?3Y0W@xIyfZfnDJiMnU+T2968G(O`R5nJtd*P% zI!0Eq#G#C=?mdWX;A|S3;$!Fq?{rQtPM#8m#NiXMTvuzoe{U|}?5v_Po^W%SyVRQ= z;#0For~s<0IlGnXtMlS<=0-vVV569%fLT(RoeQ z@Ns3ezP_GtaAI=8$9H&hdvk>C?v~g3woRNRaqrS&j;jb>g^O=ME@ffhcztzH`ye%B zs~`$h->b?1zTbo>Km4diK#9Hy0^drbfk2;$SkOB9p7)>aE+}GZ)*FppLW&;jo$a5@ zRNpv>^fWg$#KX=LS;r0>2L}f$Hl}lO7hd`ywvFGpM8`EYx?AeFySt-;#gvs_?;bxO zBeN0-oSVOZLnR4B>LGjP7N#YmS>5gPjp^yjmzNJkdZGl~OI|AA=iz|;`qyyDd4j_q z5E7aYFn7Zzj}+35yv}yH!A1j{%6SUp)PAuE2@!v>f9+p;D)N?tgLK!**x2}v&+D~d zi_jq}tMf47osbrO1$9C{8T(K$d1!TYd`d}Ga`OBIc0zgit(mp8_3Q71C{#Q=xy6m{ zb7v_IYc+RPxkNg;DV=;}2kR{qAW)?0GjZi-IT<;UcMV>G-L=CBe`&dTw7e~X!Sdjo zTwL0Zi8C^C;CTl^3o$3WRJ;-3bW^NrBf*<%O5SO#n{PW^hIngKm&w zE%-Cx6I)0}zHPOmf+w9q(2Rd({<$BCJwIV+P4aG)GjZB*UP#UO!H5S()#;8iUvOrl zhx?Yy-B&_V+yLvYUz3W-Z%aCrU-3i|cI?)g!nkhiB4`ng^z_U5-1R{abjRW-HEZZwDRAQ-wko5(+Bsj;EPbf^s<<&p+hnmdB&x%j>Ah_uSmR|oLn3Coe6C}@|mH&>QcwzHUr z+dJ&+Z^o^cdItwt7#W*Acy&t)>wYL=_6`r{>En{|h}CAzqzl{FQ(En4XsF7yRKDUO z(Bu95(JQK{qkHp@ukt@ZY5OVUH9Z2x;(8hr9evk3^kK933qOzlZI-?w%yj3Bloc7) zuwuszKn?l6b#R(Twc*A-;GBx70{mVps~6ZrSYTi$`V&}w2--knV`JZ0VS?r59r}quII50=AFbTP$Hk3f7cw%oLDbYHYq%q;A;srTmVJFPZAG!MVuDB&cZ1Zo zKx{apx-?7%_7_+Y78tN2CuP-aI<5NpcR`l(CED=l==ZLuxwAxRA(mPB`BH4t(*tE? zKVnW59qsJ;78;P&i!yz^Ea%KC=M5N~j`&S=i9al9LKa#GgZ_aIaQ<-|p*t&=bh2J%Q9EFO7Wno}<%_;4Hj*nU~QBj1{lIObA~@Cz%WgDERIS{xLB(MOw47T50cW8F$ili@nMv!^)C5?EUNOwXCi6 zdS>jas;Y)l`6;*@AA>t;oeumj{b&DNy(*>ysT?ZO1#WtJod(x~SP4XO zIypGn@j0AJiHonDY|zH!4LLZJ#>B<(JMFvcdA25b%K71E0#|yv5XLiJjD#oD{yW>B z-CS7l@9zC6@VAFhv7qs{D)-3PL*G!obvFqKi6%37tlY48GZeVhLL>8J62+$Zaa%Fl9>s08$enf<>(7qpIe^{ zw^YB--mE(*YStW|n4p=ro14;y7vDfn!67b`B|$T<-@NIwI4bw|f4K5c;0npCSzUhm zQ7g^VhGJ={+LFj&cdE*wE9Dts2<`fP1jH|>krr%)xV~;;71D_aw}m80VsdZp@I(4& zgejuJcDA=sQGJK?Yh#m=)KyeUa&xEVdOkyw`ty~k{gB;jc8V#un0OJ+h;v&P7x5Zv z-K)gJq2$+AXS+Yf#>Q4_z<$|Fz0*ym`mUELeSM7lO{uBa8*>`Ngxu8=m2(0D7%|Z* zg@uKPQ&(2KT@KSTxhR%S!N{qO-S1IRbv7--CE33L%BI)!SRJat0|eck)<9;x26B(z zJx6Yun$MrdnVah;<6tSNs}TIz{L0)6+EtZ!7?MgzD)IQ(Zrqb?UDae!`U&3@7C7kjoZ5Tor4s?r6-f>N_4msTG%+CF+<7j@C{N_H zKs!1*aET4Tw6t-FiH@|)KGw?u@T#nCfW#dhT_9{ep<|@oI1akD!gU`d@fb%VZ~0+~ zM8J%c;8z$Tl>-9}UZnhjj|NeEeVU~Qm}kN?o-#p2r9+T!AY`T0=Kpg-K%W@>(Z*M51KEs2Ym z1Zh&HasKQf&_A$q<7aC<&mu(tZhk?*ipmS1E*gr17v08k&(Z%G?uDo~PyluPq37PO z|8=Inn<5>8gM;N7D20GtQaqxfXt}z7`qC_nb%uO*Y>lA@{KQ*KLc)dOaB!$uUsIDo z!R?=6YVOJJzZ>+w%=iCpirLyk0sU-Pt20RHxy)$N?pTtt_FRci5m;=wYcu(k@DPNL zj_Iwqbf({&NAxI!e2>@px<=l@*yvDD0My8GO^$Nj0oluxVxy;bcuw*l z5E4E&wBh$SK^Ap|le$lVdbOBOFAIM)Wg9zf@4iXn^Jt#$ z_h_GudowoHt?=-Ph@5;V50is}bbtEX#KhEMQv$zxpyr<`>}yd`C^Hfh6Wg*3lJ`u! z%*^F5iMc0MXDY^IKPl(Fln4uxkAfR+@XR3CIbQnZ7t$x#)qYYp#02;AZ5&^?yId&v zMV4jx?{R;L&7?PDC;*$FJ&hTcz@d=Cl$P#S+CBbJ79}i6K?Q0QwR(*IP4V3x|5K2& zu(s|6|L&R#RzPSF{X%V3oun2}*^0?xIGoOn_U#A`_mOf$2Bf#ThOx(7L= z)_THE#Fgdc2Q8}yO2NKVux`6r<35=Mf|zlC&Bq318hh`wu7^;Zoo3H?^TJS8Z?1jN zyqyB5R6}G!xZJBp0yyXe#4!P?ytw62TTtc_*aUO}nJ6 zg}cc*B0VvA!^hac2`+;v@2pjq1e#nLT~}~$Nh}@Nx%W(tt!`3z(u!7Z$d?HMK){ej zMn=V55x9?=9O&3X+F3%HY?&Bedd_AaAqs-(wKS>xN|*`@CSSd>_=f}RT5$yfbF*Kcwapp&0pta^2Q z@@+pHI7uHh%j!_Co^Qc@jA;BRF077oXXlI#`;B%-jVfO^pkjLPsfq7K-0OaKnypg; zDx34emB@wSD-*O!$oy-RpKfiL`SD0$6wvHCUJJ;~i~*0R;Xbczw6Fb!0@_Q@j*Urn zq+4_BnpSS-ab*S_3M>;o1XcR_N-8GAsZVPw!Y8M8H_Jdjue`PIf#&B6>rcQB$nbA8 ze1Qri;GkFjfAA_4EcBgI*1o9E!-Tx)<7P_f9X^Yy;I>yWYlzG~NLcB*B%U`oG7Lw)U2immi;)S^@&oj+*1yP~z-m(Ty9OES(;otCk5>coc}98y@LCu?>x|>o$Wx&k-auOSI*h${rmE& zFyF5kK|y9-iZEJI+zmBx5h-cuEv_Z#9(-wjq-UaYlA4Vz4XW~NZSR-GYoqMcuVq;k z+Lo4$hYN%)1bGr+WfNR)$@v}IdSmkD_TB<(y-vu+z@(E5C_Ov`X!oTtkf>V8hs90$ z#qY6xi|T*UF`9T?9W^~fwZRtC|D>em%wsmEk#UF|JBdAO!B}%FY3%lCF(VtBlbtQa zt8_1;jdp+WyUj1Ew}N|gW0A7Km};ATzmmJ z?V~9QDuP3acH8E<;d2|yT@)=5&4hKH3)>5B$SZQb`P;nvfPxoY{=yQPkzu<(?}F#V zB_bvC@i3XY;yPXG5@RB`Cgix4_G!f}$jSr7!1)$|6!g(D$@i8qW*IBOOV@&& zeWol@ubz&rj8$!`n0MI!)m`=6sXtZ8&wQE7DLNkP_u|DS@ARC)4^9g}i&XUv%xQw(kK$@FVQWca{MGXywJ%u5Vs&92aK1NXc<7A@~i z;0Z5l5XW&6QP&mMaZ=vr#K#{-Q!*BuCQFo79`aV(Xx2L@>sY$Kx9dA2Ylvq)(P3bV6u&Am@(Q&`;13z?SV^mCiDj=?LccCyApqAs*pLT6r7kE1x z!W6NO1uH2i=pAO;9PiEsj<=}jB(C((xW6TF<8@7Xda1bWML8|Y*yLR3&d z%j4j!7<#kt@H5kEGztOtcNXi9pA;Dz_fAOvR;3J*r5Tp(Szs>j4-nI_7?qK4E-H>i zbY>28SFLf$9^)BzW5Wh}8hIg?yW^&)!66jJMn;!c7^mBlOegzIQ{sdnIT@p(A3ku> zpIa&`+v@1F4W+nkhV}IKH`H_6TyJfZ9tJjEUwi*tj;vr~wR4D$(XQ{<3BVEjmdvlN zr1XL)VVTqEHY%%oX_5h8?7dy)M=xm0uBz^oX7lCS^U}n)4dVxWligB&|2}uW1zd1& zaNz7jfH3TUp#|10cbokqOe3(;b$P(a=ZToEgcUTU z3~qDmY-MnAaO^Es2vSl8wy$Yvd>@9`A0f0vV3~qlF1zPJZEaM5bhX3H-lBQ>JZaGL z${uk25ue`=G0WT|^*0u#r|AP9sKfM5bSe>_hWO_T0se=?vZtJte3UoB4o-)7)W>h- z-m1*nN(x#|JFqeBG(ap|5lUW%lt?(s+)IT@G1vQ79;;L8?X)+b0@SMvvJ-w7D8*VM zrzUPZmL0S+@ULto6mf%yM8frirCxI(!EohxDWkbzRbPhZ;Jo&E*U-{ zDDu$z#J`w|uzVga3N$YYU~t`@dmyTMPIo>WEH*45mp_R}xwzEDgj{)} z+)z`q2B2Rg*u4XC)2qbG@-_W6KIcuR>xjToSxe2cK3GdmO^XGxSu`Nxw%o;VVv;GQ z_$5@Y;Wn%RK#5If)wxK#b$#ue>}>PTTa!o5fJ`RnIuOCis#WgZ9wCVJVG<5Ua$dZl z<&Ym?Ld?5_fT`(&0i7p3BPSu|#2X3+bd4itSb;ilsj7~3Zhmh8M8>3~B!F~L|MD~y zy?bWqHNLLDSZe_sB`$pGpzYl69rx@N2D(daLtH{aK!BuPJ$~P>qfIqnp8$p9442iX z>si`x5zNNi{LQNor_!2x!KAhf@UcPW?uhZx(U*RWg{;)=&E#ZpfGVY-;m*sSl9}({ z)#Y>Tv%PJOZO`6%Ci$4e@%(TUexN1sP>=)o)Q`?gRMnjJ)s_oUQBgl)lMRd}`a3$_ zad61j3jh2;AOq-O&&|xtM0&#awBuQIpAnIy#U__~bVF%F_|=*98OxiUqw@YU4Aap~ zmp|5)NdEgo(J}9(EyWx;1!;(*9ll;pR#8@Zy3+OLXu2@kmJFb-sHhNxbQb;PQN5p@0F##6761_`BOJOF2HIt zr7vk@60@>yi0^&|;86@Ea*Nh3N-J1zJ%v$lUO%%t(-9W_q@&{l=;e~(7*;o z6Bj$8r+*j^^D!H;p82#(X*b3XudcSv3gCtJC72`ZJ| zbai#@@jBwXAt@{}HQHD{283o?M;rjm$~0I~+v%Zr8$7N_{&&J=mX<~pGl=8KA~+ur zi^q3+yc))PwDIT0C))+o@_l1tj~Z+F2=Z+_^53`h#&aD(@lrs#fB3cic_8kG|A=3s zzW~s3Ci^E-PHO}98Pqk)gMsRW1bkQO)jwqTKj!8C?>zbcO7#EhfVX&fu300Vba}v^ Qjv$bs5n*NC%OA6#)a%NrHf&Af3=5NEa0g(n2o*=`}$*0RmVM z5$S~9q}PB*=w){9otZUj)|+`V|Gc%{KRLE)6DD=yYzsmefYU^ktVLmO1yS~Gx^IiC3_E6AuaAUr3t9`A+#&G$mq@5cqy0$0$ES6aj~^^V@rgbr@lf_K!DC8 z3GyywuXD!U!86E%c-T>At;7u4EZsgFABuixrwqI&ki3qGO&@R5ZSNO3zz6Oq)VlM- za3KZNIaQM1iS}Qab^qBB) z?b79vu(xmfLtaie%;VEpE_om#7#ZD*rgH^&*Rt6xe9X5;J#^DhI@mq9g?4&Q!2Y)I z<;%CpWHP6MO|0*hkB--}Un08wcgneksUG^6c^T==3Y#AXDyL4a7@GOlFZH?ge#lUj z_D{6P*x#EznAG51HnP*cZ<57gLFS#2>^YN@QT}N1lmSf2_I~d)Hi*e6I$D2&L)=(G z!uA~Z=KABny`8M>FnG`yJ{`UMl1)QcL_}l)<0WXR#3?%x_)usoHXI&6$h+VsB zc=Vev?&Emp8BNWt`OH6?7nkSLw-;VUXSKf?a^4&+5_A0CSMp=x_|}%`*(TXq-rz7l z6bOi>VFS%hTtbWeFj5K%vudf&esgKOY;d7yv3zrv?Km2$eeCe|YvV!kkhhZJ?95C) zI{irAePxtwUTO|}nDrKp66}#%=qSMkIm!gb}DgZW=I*>R$uoGPu)Dehyw=5==y@a8oNElz@ zvJ$7P)WAO7O68zUTRcjAKSGy%{E&|G9Gi$Jf(V`rYBIN4SZ8}DKunWV59hU=nVU;S zX*`ceIs^zH+tbs_@LBv_Qn^javT4PJ^wiYU4^&UD)I0oGdi%EGXb0h&uG?Pn7U{mn z*k>G{gs4S>Q@^c^4X3V3Z+TNGSJ2K#%{VIy3#wFErI~OOgg-}Q;??AJqx?VD0t0d! z(ON-+HLXq~rB-OduYB#Z4y3~5 zHVypPY&~8pL7>uaz#C^)dN*)@EXi7Fj^Pc+#S?CF(WmFK9hvGWWYTA|2IuwnG^e}|mJ9Se2*~uKmyA;&7p z`QF;9FZPZd4_gZmgzatTS=fCHahY8lQfE>gypQ8^&WRT>{k9eABF9#Fq`t_(L4TI9 zJ36bybM6O+nnugQLie9x$*HMD9==zvT>0S@sn{c`Yk}hntohQZ2BWaB@L1u0JN3%z zf(18W9@cdm>!<`!D=2d6^kgW*7-iw=D)R?)IwP-QwuKJXpt%v=r>lSlE1P-K&eh@> zPs_s;7vG|(>1m^K8*K)@xjaquSdN&0F8uzF2Z!EA_5DG|D!AjLAWZgkc%j`vjRfbf z1HGZ)VOD}UrmVc2lHO`4gC75+$n=s_tgm|Z&6}rR(Yx*;1T&liNpY1_2p9h4p>Ght zKkllC$HO=Jj0y~P*pA;8-5LNK`o4l?k5jtw@^k7Cq4qyB`P~bQU;}AMy-^=&&#mr| z>x6{?hIy`L)N+#6L@F8#ax~}M8v_ISN-X%W2eYw^JoJ6PrUfdFn{K}hO+$b*Rz6rr z*VNJJaV%*tE8go=K8`$c*^))JJRp=E9Gq_JEwAL1-Oy`Kmic{HHuR81IQiqw#%$Zu z;b}28?)xtDX0dlI06-u{D+{oZSEs$1DZ^mL##Y^-b**-OXoNF? zH-9znSt2>kPP$f(l1T8eT9%`}KvgtaDqqI2-$>VM4UzrK+Sa(Gh1N(+=^Uxff+pU6s1=2eW5! zlNDji!>ulWgqyZGJ$bm#l>g9FH*AZRj)}!ju{txKiWl#tS@w>xJlx#UO#>R3*!cKd zVI&3$Q$E~p?Up!}D;%IK{3<3WKHZSc$<8*ec=dvN;4_N1ySHJ!RFQg zy(G(dc)v*t!>lZSjZC^>Rxvom=&i1vYtw$+XXR9Bb#?j9Vf92{`3|w>U&HQ8XN)<` zv_X28bZi?qc^h+@ag$)*GCm$qY1eH@jjYa*yC#o{LgN^ zmdNxdXn4@B3(4JmzDgqVnt3@WpfO$pyBNwxl|=F4#USb>^6mh*OSB6^r!r^3;vn z_x@u5=kC-Q%JN8E*R&iUe4F+?6FR0-*M)>&WCn8YLU&eQ9=h{RwJ@0=c+YK*o{_P8 zlcU^oa`N&+0w!BMTygVzn-#@q+xcY#@k14l%4**z;hg8x42=Jpxqdh&snUkx4j?P* zpU_Ar(Xdww08Q4;dvN%%Cd%p=D_<(e_NMgD+VhaI6A zp^QSkr6)kXKDz%*Fa%!IztCPSa9uzkuPNC!jf_u{${pLhO{KQsJ!X(m-`&vjUo`=jT-+084}6LYrEy)FE3i{H^&BFR20_Qgr8^ z*2@r}6PpVEpjv=O@H0)I>BIGtkHE#4<%Br66x08I^jLF4F&zXl+kCM5`y7f-+25yh zKO#Jw6w1IgGe0k&pipPIO$(`!0}~s)Vqsz7Atx%T1Gau#b+zm>7RYywzZ-zF@E8K} z-b@2>I(UP+T>Lkn(7TNX8)3k_%uY>Z0%|TaC;GC=eEj&)CcLPqC|TOxvI?j}A#riN z?(XjI9UTbD5{ZO=s6eQx5tNrV8L4&|2&SR^@rNjEqzV;7BogJ(WPgoF(CJm)5Zd62 z%*@Q$lar4sQ6mVV-!`H*SG&@2K(zVGmqZzj2=4A*(=F7uQ5JhOczJn;8-p5$8bCWg z6#A{LO-spdD;9^t1s?6%0tuRulA4;zeBr|5e%mKc1g~9F2T!1P>}cNmPce!^Vxv-1 zxlhs2Eq811!LjmQf6HS7OUuj%UZv}@va&!-3xe75$a{)Aa>_avXJyat1{jYs1Qii2M->A1*~n}TOt)Le|dAsM%&xFst{geqi+cGh7@FE zaByLi;8E-ID-(mkFmrJU!+^ny!(y?Dii(~GJ8t{}0?YV1vVpO2BDnL!%F06{A|_~! zCN>VUx83mN#CrOy%3lyY#QD!Pv%@ONx6lD2qoZc1;R=|XoSehphYX+*bJW8*?Ljnb zR+Y9#uOg91tel5BFke94hYVM{ScGU8+&UuC>5WU!u|qUphlPc4b8~zC{B$ae!X{=O z3&QS+(tPp!`ST8}g024V5tl|7=HkVRo{K&FUW+|yKupAdSd^7*jJu&gkDoq$VrFH{ z^;;t%MMXt(e4QZcNRWiV7cFgV^XA%99FLC<-$q0r+}+Cp4=G6OEw?)&CKck2$uf?) zhrk(LzYc&Dwl|w#!OX!S1S>Qy&2&PcSU5QgXJ(#Y70^0hpR|^Xq0#7v1b4N|moGc? z<#+R_ARvOBK4%yqnq8_)N>Bv_h1HR2-GYLGLNh{{yw?&GguFagEsWJh8=y9sxNYvW z&~=+j);TsNCZ^VVwJ(w2)aw!#@G;Jc3i7`@aAc|2C&|%Ejfyjwv2$Jp=Jlp$S|* zT=zedb)&h3?P<*K4z;=`GN}*W)-zG78j|GyBiv11^xRp$0IbYFpMCGS;>9(od(K{C zacR+R8ZA@|EjrVb^QiedN%CF&8WK2qMALYHwd6I>m6;ku^wfN(MO5SQxZ*Oh*i3{i zLGwQ}jn(?v4{gM;F1vepR60;hDBEP@dgzF^<|XAQb9OOpAB*AAqOQX)Ie80N@A)V* z@r%Ar)6C%`jhfLy3BUVz6n*Xm?C%z4Xc)?%`tMxT2-i0=8`U0x3IY8)?(>^RX)rTf zkyEyJ^73aEUf#!5T%j4jhP9PHq?555yi0p}*M8giSg}`;Rn(+70!y4Bci+YGu4TG3 zK6G~!xCH3t#f4z4k4-cU!xc}B+T4&_!h*9@!d<#Pmo{(LnyHWtb^@iG2Yd`i*gyYoOmwvDH$s6rsF^HGYL zZ_tg(dzFOb3a@Qjqi@{9&!fAet>Qz>=Iju~ zWb<{VeKSnJU@~97zNfVQm*E()u_5$4M>d}*uhh@rY9 zx~)|p#4Dk&<%a$7mAXJ;ZQn)O7l2zGXqibAn)5Z!PTKle}Zb7G#HI#Ca$qn|9h>CPE2j7cM>O`?ya_s#N+3eqz77Vr_3C0F3)jKf*_d?ZLl_-X<6&9 zUFDFjh&&x08T+2Qqop}wY=)_G&^1^ATx4xQZi=S zkk>_(&C}9Q(tR!;)FU{p`WJ>ByXM4mR{d z;t;k6{{GRj1PRE*+vVP?nuef|rWQ3;g?gplAdPsOA&@u){(Iz^NNdJ;>Z_XllctRm zBk4uG_xUP(_9EWR^2I23y@?T^kY7~8ixApa>7XPNu@7t&u_cUu+g>K(raafA02sZY14--V1M;u*w%A8CXoIX zGMjdVx$P!)Gx-v4!@}xvwgv~ae5c#dn~%b7ZrF|9fBxz`(#Pio0r!|a84!o!h?m~; zP;Kc)QR&`mTb)wc(>7M%@Iz3HO5~Fe3$VK%fTGk~?U>&|Trwupd92pBuOn5)RLV90 zw>`06=S?0g1Qq9=z7iX|tpN`x?CjaIoVR=uO{$zSaHcmMDBrDXnc4CNCVb}Ohg-sf z;2C+JD_lPYRa*C+mb)dt5H~O`y{%%s=}Hh!9#Z$U*SP-_+V7IjdgDMx_=2L=!=bse1uY7JQ0dEzu|w;V^uT&;pIhJ zzeP}Tp<3S!-;J}RA|w*yJnZbU_CDR)8TshZ`^-#BPiet>p^pWngtekCM1u-kylqN9 z>%PnJLRPYr?YKx}H{g=10Qv%>%>ElLX*I0-cX$UN1zl_tQzi|TirIHM2k|H}=ZiI@ zW=dpfEY1G10>1CC=N@HsJ_~R!Wp%`KbwuTw1)mE8pM(J>)^Amt1ytQx_baKK;Z`mI zvL%)c?Bl7WB?sm6XuG;~_pVv7B1zD6Jl_LSsGHBJzRzn)tDuP}Uq^Pyk!;K8XdW)>C^V4*0h zy@oFJM}@#CL1AFfy(>c=pbyb``i`Z(f~;%W$@!e5qKS$zl}9Dl8Wdpj9chN;bL}&| zjp^gQewl>hl@%v#>`m3Sgc2ig!lkG*VwXShD4bg+_n)Kz3CzzP+*VXvd?W17I@Abb zxW9ezr#rgh&4BR_X2t0SzaLul(8;@PWRGLltANWZ=*{a~h|yI+4&cpSSWwS7zwkT~ z21kuiiTRz%`vxi1PH_JNYj}|fe5?*ldVwA{YJSVo=Ia9zgg*k}G)u+Srr~u~5GROn r943YK2i6FD{hvYT|H}$W+%X-SkA1#ZX+anm9|WeRa~FT-QOLglkSqyT diff --git a/playwright/snapshots/file-upload/image-upload.spec.ts/image-upload-preview-linux.png b/playwright/snapshots/file-upload/image-upload.spec.ts/image-upload-preview-linux.png index 75a9c353deeda4f230f2920bb436e94936199504..c18808118974eaf636f751bbc34eaf70f94161d2 100644 GIT binary patch literal 70119 zcmXt91yoeu*L|cQjerPJ64D{vol?@>NP~2DNec)_OM}GFJ#Z>?Fx zyqSC7z31G0&OT?qFhzMuw3h@g0RTXgmJ(9}00i()*ij@{@RtyY3=H@S#!*R91SlJR zy9WTIfV7yfid)*@lDn6(>K*f+o#j-hMJmUVy7?aZcf3MH4oz{Fb_6k#u`&C}vC{<2 zv^UOgiPFbT3s`faBmD?d-@*20zbh<()%82RZ(*{SKWw;e@m=HyVv~z!v(R#D$wYT_ zOmdVrieG-eleUA|bWP;zjH)1{TJb%Uhty@YMTsath``!VHcb*ikVtX1V?+s!@&CY zVUaR+EjWXnZ2j|e^h;cFlZBsIOxNYUUJ)$J|6T9=T#eX z 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 diff --git a/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png b/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..891f024bf823661d7edcc62606ada92993ae79dd GIT binary patch literal 17851 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 Bwjuxk literal 23503 zcmcG$gFy4tM7q06Ktw`mqy(kAyQM_B1*BWLyQHN%rMsJV9q+y0`wx8Y z`SJL8j{EGr_S!SX9CJ)Um6c@CQAkiA5D5BfIVlwg1P%)VflWk00H3T|Qrv_8!Pu+F zN4}zJ4Xg zV`_R2fA1nKCN3_H{qj%Bb1WP;)Uw!7>OduwomqipN@|683nx?F6ei@Wk+6ZG!6f^1 zep4ss^fWrn^LYVr2m~1dLyP1?^)lJ&{5BouuL;M?FO%o8Z@jHt>gqctC!dv;ve-B~ z`s%R2VMAZ)7$xt67w%y}fr>p+z9Hb%H@0C%-1MccvnZ%> z%ppxF7@W`MVUt`(sUc{=4^}R~-Brpqh)R6S7f#ZSYT+rPYWwMXMtlh_RV;%l<5^uo z^1@w4ObqPUn-^GPY8rl^6xzgvu_EEQ%XhcARcih&dGc)qIqgeq*yK8nTE^N!#5)+EgKT7?Ox7 zHhnC}>l5tYwei$LS{%DhZVcK=#CDhD59pWh{CC zOeue7OPedRi*~eImhvmoHv(N+&m$5<7%cfgD>6OI=Efeef>=RA;s0FiJD=dou|{n) zl8GX7uEp(NNXTEK#A5kSY|Et9kCnTsyvT4=uk4>gUs+#d>Eg0f8_l?oQMna4PB#V5$J;Q^nxT(7+*)`7K*^Ys$vf$TKu|hwf@gHh)RDmDwU2->R6R+Vzv>@ zzqJR;8B2Ncy~Mv_@%<*o_+z`ZH=9JFK#fm8W7|EX>DJ)ZC^;&N`IH9`6fsEZNON$&*WP_~APc*ZI(Va;zPcK>R2TE*c&4}W(9 z7PqlMpphmN`*Rw#k5yS*J5oRq`S>hwqH}1n^gl;MTXJcUpQ5N?p+$MIWC)Q)8YZr< zZo(D>2d6AD{@c~(^WgS22=n*#ti60u1e$lu8H)cNt|AnKg;nY@zWBGZ^MBRFPL&7s zLPNtUaetO7QvJJ4GWn7*600FC>~lAmFA+5h7j+GM;)QCA`#5;e1!B+UD(95qSTf+D z96$cYU@(48nHlUP?^thoM8%o&h*_^`AvEhj|GJXGzns#1t%db5Asg#|=g%(=?uy&k z+6&7@ep;IredLmVe(omhrQ1Rxi~AVZnn6i}6FTeW@O}v8}$E*oG|p2nODlimxTvhnNu|h-29tcF;<3hqi(5P{n9^?N+HV`Ua;c)UTPqibIWLLrjzZ zCTn2#!i`C@2FG+*7XpDE-nWGy$%yZ57@a43LqRj7n)vS`g;?yeB#bz?NdI;f@t4D@ zJ$uP}=pxtD)-<`Sh=O|1J8c+Gn4tG4kfx+Yu2N7}GdT6`kmNsx^~um_w_1ytfcb$O zTuLmaCxXqbtCC0zPGk;Zb^r!6$nsnhP{e-fbh4|w8#;5;ny<8@n_ZxpIBsqnFw)LUs?7P0G zxh5RMGMJQppWdXbOdW8AS-$*xYS%l^$OhecY<>+Zy-eNov5%2zjDf)^_BVSK3S9qP z0zJKSVC||!9#5i}#?I5H5%JED+{F7wHQ8VZ=n<0v5-hlm%mF1oRbJxR-7P#;|4!|| z!&_)OzHMvan;)e;8wIY!23yzL3?^qZrO;^~q^vk-SRxtk}O2-G9#uZu8x1jsm<= z7A2>$>B;VXtA030>=Vbn?BK@hqmQRQoyo{$a#9}un xjDw|-0P27@^$9sBT=;z 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 literal 17170 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

+ ); } diff --git a/src/components/views/settings/UserPersonalInfoSettings.tsx b/src/components/views/settings/UserPersonalInfoSettings.tsx new file mode 100644 index 000000000..8e5880a51 --- /dev/null +++ b/src/components/views/settings/UserPersonalInfoSettings.tsx @@ -0,0 +1,130 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 React, { useCallback, useEffect, useState } from "react"; +import { ThreepidMedium } from "matrix-js-sdk/src/matrix"; +import { Alert } from "@vector-im/compound-web"; + +import AccountEmailAddresses from "./account/EmailAddresses"; +import AccountPhoneNumbers from "./account/PhoneNumbers"; +import { _t } from "../../../languageHandler"; +import InlineSpinner from "../elements/InlineSpinner"; +import SettingsSubsection from "./shared/SettingsSubsection"; +import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; +import { ThirdPartyIdentifier } from "../../../AddThreepid"; +import SettingsStore from "../../../settings/SettingsStore"; +import { UIFeature } from "../../../settings/UIFeature"; + +type LoadingState = "loading" | "loaded" | "error"; + +interface ThreepidSectionWrapperProps { + error: string; + loadingState: LoadingState; + children: React.ReactNode; +} + +const ThreepidSectionWrapper: React.FC = ({ error, loadingState, children }) => { + if (loadingState === "loading") { + return ; + } else if (loadingState === "error") { + return ( + + {error} + + ); + } else { + return <>{children}; + } +}; + +interface UserPersonalInfoSettingsProps { + canMake3pidChanges: boolean; +} + +/** + * Settings controls allowing the user to set personal information like email addresses. + */ +export const UserPersonalInfoSettings: React.FC = ({ canMake3pidChanges }) => { + const [emails, setEmails] = useState(); + const [phoneNumbers, setPhoneNumbers] = useState(); + const [loadingState, setLoadingState] = useState<"loading" | "loaded" | "error">("loading"); + + const client = useMatrixClientContext(); + + useEffect(() => { + (async () => { + try { + const threepids = await client.getThreePids(); + setEmails(threepids.threepids.filter((a) => a.medium === ThreepidMedium.Email)); + setPhoneNumbers(threepids.threepids.filter((a) => a.medium === ThreepidMedium.Phone)); + setLoadingState("loaded"); + } catch (e) { + setLoadingState("error"); + } + })(); + }, [client]); + + const onEmailsChange = useCallback((emails: ThirdPartyIdentifier[]) => { + setEmails(emails); + }, []); + + const onMsisdnsChange = useCallback((msisdns: ThirdPartyIdentifier[]) => { + setPhoneNumbers(msisdns); + }, []); + + if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null; + + return ( +
+

{_t("settings|general|personal_info")}

+ + + + + + + + + + + +
+ ); +}; + +export default UserPersonalInfoSettings; diff --git a/src/components/views/settings/UserProfileSettings.tsx b/src/components/views/settings/UserProfileSettings.tsx index f912fa44f..a5ff43567 100644 --- a/src/components/views/settings/UserProfileSettings.tsx +++ b/src/components/views/settings/UserProfileSettings.tsx @@ -16,7 +16,8 @@ limitations under the License. import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; -import { EditInPlace, Alert } from "@vector-im/compound-web"; +import { EditInPlace, Alert, ErrorMessage } from "@vector-im/compound-web"; +import { Icon as PopOutIcon } from "@vector-im/compound-design-tokens/icons/pop-out.svg"; import { _t } from "../../../languageHandler"; import { OwnProfileStore } from "../../../stores/OwnProfileStore"; @@ -29,6 +30,7 @@ import UserIdentifierCustomisations from "../../../customisations/UserIdentifier import { useId } from "../../../utils/useId"; import CopyableText from "../elements/CopyableText"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; +import AccessibleButton from "../elements/AccessibleButton"; const SpinnerToast: React.FC = ({ children }) => ( <> @@ -55,13 +57,44 @@ const UsernameBox: React.FC = ({ username }) => { ); }; +interface ManageAccountButtonProps { + externalAccountManagementUrl: string; +} + +const ManageAccountButton: React.FC = ({ externalAccountManagementUrl }) => ( + + + {_t("settings|general|oidc_manage_button")} + +); + +interface UserProfileSettingsProps { + // The URL to redirect the user to in order to manage their account. + externalAccountManagementUrl?: string; + // Whether the homeserver allows the user to set their display name. + canSetDisplayName: boolean; + // Whether the homeserver allows the user to set their avatar. + canSetAvatar: boolean; +} + /** * A group of settings views to allow the user to set their profile information. */ -const UserProfileSettings: React.FC = () => { +const UserProfileSettings: React.FC = ({ + externalAccountManagementUrl, + canSetDisplayName, + canSetAvatar, +}) => { const [avatarURL, setAvatarURL] = useState(OwnProfileStore.instance.avatarMxc); const [displayName, setDisplayName] = useState(OwnProfileStore.instance.displayName ?? ""); - const [initialDisplayName, setInitialDisplayName] = useState(OwnProfileStore.instance.displayName ?? ""); const [avatarError, setAvatarError] = useState(false); const [maxUploadSize, setMaxUploadSize] = useState(); const [displayNameError, setDisplayNameError] = useState(false); @@ -128,9 +161,9 @@ const UserProfileSettings: React.FC = () => { try { setDisplayNameError(false); await client.setDisplayName(displayName); - setInitialDisplayName(displayName); } catch (e) { setDisplayNameError(true); + throw e; } }, [displayName, client]); @@ -142,10 +175,16 @@ const UserProfileSettings: React.FC = () => { [client], ); + const someFieldsDisabled = !canSetDisplayName || !canSetAvatar; + return (

{_t("common|profile")}

-
{_t("settings|general|profile_subtitle")}
+
+ {someFieldsDisabled + ? _t("settings|general|profile_subtitle_oidc") + : _t("settings|general|profile_subtitle")} +
{ removeAvatar={avatarURL ? onAvatarRemove : undefined} placeholderName={displayName} placeholderId={client.getUserId() ?? ""} + disabled={!canSetAvatar} /> + disabled={!canSetDisplayName} + > + {displayNameError && {_t("settings|general|display_name_error")}} +
{avatarError && ( @@ -177,6 +219,11 @@ const UserProfileSettings: React.FC = () => { )} {userIdentifier && } + {externalAccountManagementUrl && ( +
+ +
+ )}
); }; diff --git a/src/components/views/settings/discovery/DiscoverySettings.tsx b/src/components/views/settings/discovery/DiscoverySettings.tsx new file mode 100644 index 000000000..8b1a20ac2 --- /dev/null +++ b/src/components/views/settings/discovery/DiscoverySettings.tsx @@ -0,0 +1,190 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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 React, { useCallback, useEffect, useState } from "react"; +import { SERVICE_TYPES, ThreepidMedium } from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; +import { Alert } from "@vector-im/compound-web"; + +import DiscoveryEmailAddresses from "../discovery/EmailAddresses"; +import DiscoveryPhoneNumbers from "../discovery/PhoneNumbers"; +import { getThreepidsWithBindStatus } from "../../../../boundThreepids"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; +import { ThirdPartyIdentifier } from "../../../../AddThreepid"; +import SettingsStore from "../../../../settings/SettingsStore"; +import { UIFeature } from "../../../../settings/UIFeature"; +import { _t } from "../../../../languageHandler"; +import SetIdServer from "../SetIdServer"; +import SettingsSubsection from "../shared/SettingsSubsection"; +import InlineTermsAgreement from "../../terms/InlineTermsAgreement"; +import { Service, ServicePolicyPair, startTermsFlow } from "../../../../Terms"; +import IdentityAuthClient from "../../../../IdentityAuthClient"; +import { abbreviateUrl } from "../../../../utils/UrlUtils"; +import { useDispatcher } from "../../../../hooks/useDispatcher"; +import defaultDispatcher from "../../../../dispatcher/dispatcher"; +import { ActionPayload } from "../../../../dispatcher/payloads"; + +type RequiredPolicyInfo = + | { + // This object is passed along to a component for handling + policiesAndServices: null; // From the startTermsFlow callback + agreedUrls: null; // From the startTermsFlow callback + resolve: null; // Promise resolve function for startTermsFlow callback + } + | { + policiesAndServices: ServicePolicyPair[]; + agreedUrls: string[]; + resolve: (values: string[]) => void; + }; + +/** + * Settings controlling how a user's email addreses and phone numbers can be used to discover them + */ +export const DiscoverySettings: React.FC = () => { + const client = useMatrixClientContext(); + + const [emails, setEmails] = useState([]); + const [phoneNumbers, setPhoneNumbers] = useState([]); + const [loadingState, setLoadingState] = useState<"loading" | "loaded" | "error">("loading"); + const [idServerName, setIdServerName] = useState(abbreviateUrl(client.getIdentityServerUrl())); + const [canMake3pidChanges, setCanMake3pidChanges] = useState(false); + + const [requiredPolicyInfo, setRequiredPolicyInfo] = useState({ + // This object is passed along to a component for handling + policiesAndServices: null, // From the startTermsFlow callback + agreedUrls: null, // From the startTermsFlow callback + resolve: null, // Promise resolve function for startTermsFlow callback + }); + const [hasTerms, setHasTerms] = useState(false); + + const getThreepidState = useCallback(async () => { + const threepids = await getThreepidsWithBindStatus(client); + setEmails(threepids.filter((a) => a.medium === ThreepidMedium.Email)); + setPhoneNumbers(threepids.filter((a) => a.medium === ThreepidMedium.Phone)); + }, [client]); + + useDispatcher( + defaultDispatcher, + useCallback( + (payload: ActionPayload) => { + if (payload.action === "id_server_changed") { + setIdServerName(abbreviateUrl(client.getIdentityServerUrl())); + + getThreepidState().then(); + } + }, + [client, getThreepidState], + ), + ); + + useEffect(() => { + (async () => { + try { + await getThreepidState(); + + const capabilities = await client.getCapabilities(); + setCanMake3pidChanges( + !capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true, + ); + + // By starting the terms flow we get the logic for checking which terms the user has signed + // for free. So we might as well use that for our own purposes. + const idServerUrl = client.getIdentityServerUrl(); + if (!idServerUrl) { + return; + } + + const authClient = new IdentityAuthClient(); + try { + const idAccessToken = await authClient.getAccessToken({ check: false }); + await startTermsFlow( + client, + [new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)], + (policiesAndServices, agreedUrls, extraClassNames) => { + return new Promise((resolve) => { + setIdServerName(abbreviateUrl(idServerUrl)); + setHasTerms(true); + setRequiredPolicyInfo({ + policiesAndServices, + agreedUrls, + resolve, + }); + }); + }, + ); + // User accepted all terms + setHasTerms(false); + } catch (e) { + logger.warn( + `Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`, + ); + logger.warn(e); + } + + setLoadingState("loaded"); + } catch (e) { + setLoadingState("error"); + } + })(); + }, [client, getThreepidState]); + + if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null; + + if (hasTerms && requiredPolicyInfo.policiesAndServices) { + const intro = ( + + {_t("settings|general|discovery_needs_terms", { serverName: idServerName })} + + ); + return ( + <> + + {/* has its own heading as it includes the current identity server */} + + + ); + } + + const threepidSection = idServerName ? ( + <> + + + + ) : null; + + return ( + + {threepidSection} + {/* has its own heading as it includes the current identity server */} + + + ); +}; + +export default DiscoverySettings; diff --git a/src/components/views/settings/shared/SettingsSubsection.tsx b/src/components/views/settings/shared/SettingsSubsection.tsx index 035306f5f..afcf92dea 100644 --- a/src/components/views/settings/shared/SettingsSubsection.tsx +++ b/src/components/views/settings/shared/SettingsSubsection.tsx @@ -16,6 +16,7 @@ limitations under the License. import classNames from "classnames"; import React, { HTMLAttributes } from "react"; +import { Separator } from "@vector-im/compound-web"; import { SettingsSubsectionHeading } from "./SettingsSubsectionHeading"; @@ -25,6 +26,11 @@ export interface SettingsSubsectionProps extends HTMLAttributes children?: React.ReactNode; // when true content will be justify-items: stretch, which will make items within the section stretch to full width. stretchContent?: boolean; + /* + * When true, the legacy UI style will be applied to the subsection. + * @default true + */ + legacy?: boolean; } export const SettingsSubsectionText: React.FC> = ({ children, ...rest }) => ( @@ -38,10 +44,16 @@ export const SettingsSubsection: React.FC = ({ description, children, stretchContent, + legacy = true, ...rest }) => ( -
-
`; diff --git a/src/utils/exportUtils/exportCSS.ts b/src/utils/exportUtils/exportCSS.ts index bd7ddac01..15716ad54 100644 --- a/src/utils/exportUtils/exportCSS.ts +++ b/src/utils/exportUtils/exportCSS.ts @@ -14,74 +14,80 @@ See the License for the specific language governing permissions and limitations under the License. */ +import type { Rule, StyleSheet } from "css-tree"; + import customCSS from "!!raw-loader!./exportCustomCSS.css"; const cssSelectorTextClassesRegex = /\.[\w-]+/g; function mutateCssText(css: string): string { // replace used fonts so that we don't have to bundle Inter & Inconsalata + const sansFont = `-apple-system, BlinkMacSystemFont, avenir next, + avenir, segoe ui, helvetica neue, helvetica, Ubuntu, roboto, noto, arial, sans-serif`; return css - .replace( - /font-family: ?(Inter|'Inter'|"Inter")/g, - `font-family: -apple-system, BlinkMacSystemFont, avenir next, - avenir, segoe ui, helvetica neue, helvetica, Ubuntu, roboto, noto, arial, sans-serif`, - ) + .replace(/font-family: ?(Inter|'Inter'|"Inter")/g, `font-family: ${sansFont}`) + .replace(/--cpd-font-family-sans: ?(Inter|'Inter'|"Inter")/g, `--cpd-font-family-sans: ${sansFont}`) .replace( /font-family: ?Inconsolata/g, "font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace", ); } -function isLightTheme(sheet: CSSStyleSheet): boolean { - return (sheet.ownerNode)?.dataset.mxTheme?.toLowerCase() === "light"; -} - -async function getRulesFromCssFile(path: string): Promise { - const doc = document.implementation.createHTMLDocument(""); - const styleElement = document.createElement("style"); +function includeRule(rule: Rule, usedClasses: Set): boolean { + if (rule.prelude.type === "Raw") { + // cull empty rules + if (rule.block.children.isEmpty) return false; - const res = await fetch(path); - styleElement.textContent = await res.text(); - // the style will only be parsed once it is added to a document - doc.body.appendChild(styleElement); - - return styleElement.sheet!; + return rule.prelude.value.split(",").some((subselector) => { + const classes = subselector.trim().match(cssSelectorTextClassesRegex); + if (classes && !classes.every((c) => usedClasses.has(c.substring(1)))) { + return false; + } + return true; + }); + } + return true; } // naively culls unused css rules based on which classes are present in the html, // doesn't cull rules which won't apply due to the full selector not matching but gets rid of a LOT of cruft anyway. +// We cannot use document.styleSheets as it does not handle variables in shorthand properties sanely, +// see https://github.com/element-hq/element-web/issues/26761 const getExportCSS = async (usedClasses: Set): Promise => { - // only include bundle.css and the data-mx-theme=light styling - const stylesheets = Array.from(document.styleSheets).filter((s) => { - return s.href?.endsWith("bundle.css") || isLightTheme(s); - }); + const csstree = await import("css-tree"); - // If the light theme isn't loaded we will have to fetch & parse it manually - if (!stylesheets.some(isLightTheme)) { - const href = document.querySelector('link[rel="stylesheet"][href$="theme-light.css"]')?.href; - if (href) stylesheets.push(await getRulesFromCssFile(href)); - } + // only include bundle.css and light theme styling + const hrefs = ["bundle.css", "theme-light.css"].map((name) => { + return document.querySelector(`link[rel="stylesheet"][href$="${name}"]`)?.href; + }); let css = ""; - for (const stylesheet of stylesheets) { - for (const rule of stylesheet.cssRules) { - if (rule instanceof CSSFontFaceRule) continue; // we don't want to bundle any fonts - - const selectorText = (rule as CSSStyleRule).selectorText; - - // only skip the rule if all branches (,) of the selector are redundant - if ( - selectorText?.split(",").every((selector) => { - const classes = selector.match(cssSelectorTextClassesRegex); - if (classes && !classes.every((c) => usedClasses.has(c.substring(1)))) { - return true; // signal as a redundant selector - } - }) - ) { - continue; // skip this rule as it is redundant + + for (const href of hrefs) { + if (!href) continue; + const res = await fetch(href); + const text = await res.text(); + + const ast = csstree.parse(text, { + context: "stylesheet", + parseAtrulePrelude: false, + parseRulePrelude: false, + parseValue: false, + parseCustomProperty: false, + }) as StyleSheet; + + for (const rule of ast.children) { + if (rule.type === "Atrule") { + if (rule.name === "font-face") { + continue; + } + } + + if (rule.type === "Rule" && !includeRule(rule, usedClasses)) { + continue; } - css += mutateCssText(rule.cssText) + "\n"; + css += mutateCssText(csstree.generate(rule)); } } diff --git a/src/utils/exportUtils/exportCustomCSS.css b/src/utils/exportUtils/exportCustomCSS.css index 4807e316a..bd0de6426 100644 --- a/src/utils/exportUtils/exportCustomCSS.css +++ b/src/utils/exportUtils/exportCustomCSS.css @@ -18,6 +18,11 @@ limitations under the License. This file is raw-imported (imported as plain text) for the export bundle, which is the reason for the .css format and the colours being hard-coded hard-coded. */ +html, +body { + font-size: var(--cpd-font-size-root) !important; +} + #snackbar { display: flex; visibility: hidden; diff --git a/src/utils/strings.ts b/src/utils/strings.ts index 53039cdc8..8f7446e49 100644 --- a/src/utils/strings.ts +++ b/src/utils/strings.ts @@ -21,7 +21,6 @@ limitations under the License. * @param text the plaintext to put in the user's clipboard */ import { logger } from "matrix-js-sdk/src/logger"; -import GraphemeSplitter from "graphemer"; export async function copyPlaintext(text: string): Promise { try { @@ -85,6 +84,8 @@ export function getSelectedText(): string { return window.getSelection()!.toString(); } +export const graphemeSegmenter = new Intl.Segmenter(); + /** * Returns the first grapheme in the given string, * especially useful for strings containing emoji, will not break compound emoji up. @@ -92,7 +93,6 @@ export function getSelectedText(): string { * @returns the first grapheme or an empty string if given an empty string */ export function getFirstGrapheme(str: string): string { - const splitter = new GraphemeSplitter(); - const result = splitter.iterateGraphemes(str).next(); - return result.done ? "" : result.value; + const result = graphemeSegmenter.segment(str)[Symbol.iterator]().next(); + return result.done ? "" : result.value.segment; } diff --git a/src/voice-broadcast/models/VoiceBroadcastRecording.ts b/src/voice-broadcast/models/VoiceBroadcastRecording.ts index c36e3f75b..da45a50f8 100644 --- a/src/voice-broadcast/models/VoiceBroadcastRecording.ts +++ b/src/voice-broadcast/models/VoiceBroadcastRecording.ts @@ -47,6 +47,7 @@ import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents"; import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper"; import { createReconnectedListener } from "../../utils/connection"; import { localNotificationsAreSilenced } from "../../utils/notifications"; +import { BackgroundAudio } from "../../audio/BackgroundAudio"; export enum VoiceBroadcastRecordingEvent { StateChanged = "liveness_changed", @@ -75,6 +76,7 @@ export class VoiceBroadcastRecording private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync]; private roomId: string; private infoEventId: string; + private backgroundAudio = new BackgroundAudio(); /** * Broadcast chunks have a sequence number to bring them in the correct order and to know if a message is missing. @@ -346,15 +348,7 @@ export class VoiceBroadcastRecording return; } - // Audio files are added to the document in Element Web. - // See
- {typeof heading === "string" ? : <>{heading}} +
+ {typeof heading === "string" ? : <>{heading}} {!!description && (
{description} @@ -52,11 +64,13 @@ export const SettingsSubsection: React.FC = ({ className={classNames("mx_SettingsSubsection_content", { mx_SettingsSubsection_contentStretch: !!stretchContent, mx_SettingsSubsection_noHeading: !heading && !description, + mx_SettingsSubsection_content_newUi: !legacy, })} > {children}
)} + {!legacy && }
); diff --git a/src/components/views/settings/shared/SettingsSubsectionHeading.tsx b/src/components/views/settings/shared/SettingsSubsectionHeading.tsx index 262b9f4d3..936b11dc3 100644 --- a/src/components/views/settings/shared/SettingsSubsectionHeading.tsx +++ b/src/components/views/settings/shared/SettingsSubsectionHeading.tsx @@ -20,14 +20,24 @@ import Heading from "../../typography/Heading"; export interface SettingsSubsectionHeadingProps extends HTMLAttributes { heading: string; + legacy?: boolean; children?: React.ReactNode; } -export const SettingsSubsectionHeading: React.FC = ({ heading, children, ...rest }) => ( -
- - {heading} - - {children} -
-); +export const SettingsSubsectionHeading: React.FC = ({ + heading, + legacy = true, + children, + ...rest +}) => { + const size = legacy ? "4" : "3"; + + return ( +
+ + {heading} + + {children} +
+ ); +}; diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 411e75c90..5b991be0d 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -50,6 +50,7 @@ import SettingsTab from "../SettingsTab"; import SdkConfig from "../../../../../SdkConfig"; import { shouldForceDisableEncryption } from "../../../../../utils/crypto/shouldForceDisableEncryption"; import { Caption } from "../../../typography/Caption"; +import { MEGOLM_ENCRYPTION_ALGORITHM } from "../../../../../utils/crypto"; interface IProps { room: Room; @@ -176,7 +177,7 @@ export default class SecurityRoomSettingsTab extends React.Component { logger.error(e); diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 99f5a51c3..cf3db0f5a 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -25,15 +25,13 @@ import Field from "../../../elements/Field"; import AccessibleButton from "../../../elements/AccessibleButton"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; -import { Layout } from "../../../../../settings/enums/Layout"; -import LayoutSwitcher from "../../LayoutSwitcher"; +import { LayoutSwitcher } from "../../LayoutSwitcher"; import FontScalingPanel from "../../FontScalingPanel"; -import ThemeChoicePanel from "../../ThemeChoicePanel"; +import { ThemeChoicePanel } from "../../ThemeChoicePanel"; import ImageSizePanel from "../../ImageSizePanel"; import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; import SettingsSubsection from "../../shared/SettingsSubsection"; -import MatrixClientContext from "../../../../../contexts/MatrixClientContext"; interface IProps {} @@ -42,21 +40,9 @@ interface IState { useSystemFont: boolean; systemFont: string; showAdvanced: boolean; - layout: Layout; - // User profile data for the message preview - userId?: string; - displayName?: string; - avatarUrl?: string; } export default class AppearanceUserSettingsTab extends React.Component { - public static contextType = MatrixClientContext; - public context!: React.ContextType; - - private readonly MESSAGE_PREVIEW_TEXT = _t("common|preview_message"); - - private unmounted = false; - public constructor(props: IProps) { super(props); @@ -65,32 +51,9 @@ export default class AppearanceUserSettingsTab extends React.Component { - // Fetch the current user profile for the message preview - const client = this.context; - const userId = client.getUserId()!; - const profileInfo = await client.getProfileInfo(userId); - if (this.unmounted) return; - - this.setState({ - userId, - displayName: profileInfo.displayname, - avatarUrl: profileInfo.avatar_url, - }); - } - - public componentWillUnmount(): void { - this.unmounted = true; - } - - private onLayoutChanged = (layout: Layout): void => { - this.setState({ layout: layout }); - }; - private renderAdvancedSection(): ReactNode { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; @@ -156,13 +119,7 @@ export default class AppearanceUserSettingsTab extends React.Component - + {this.renderAdvancedSection()} diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 2a42d58dd..e351e46a9 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -16,159 +16,61 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; -import { SERVICE_TYPES, HTTPError, IThreepid, ThreepidMedium } from "matrix-js-sdk/src/matrix"; +import React from "react"; +import { HTTPError } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { Icon as WarningIcon } from "../../../../../../res/img/feather-customised/warning-triangle.svg"; import { UserFriendlyError, _t } from "../../../../../languageHandler"; import UserProfileSettings from "../../UserProfileSettings"; -import * as languageHandler from "../../../../../languageHandler"; import SettingsStore from "../../../../../settings/SettingsStore"; -import LanguageDropdown from "../../../elements/LanguageDropdown"; -import SpellCheckSettings from "../../SpellCheckSettings"; import AccessibleButton from "../../../elements/AccessibleButton"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; -import PlatformPeg from "../../../../../PlatformPeg"; import Modal from "../../../../../Modal"; -import dis from "../../../../../dispatcher/dispatcher"; -import { Service, ServicePolicyPair, startTermsFlow } from "../../../../../Terms"; -import IdentityAuthClient from "../../../../../IdentityAuthClient"; -import { abbreviateUrl } from "../../../../../utils/UrlUtils"; -import { getThreepidsWithBindStatus } from "../../../../../boundThreepids"; -import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; -import { ActionPayload } from "../../../../../dispatcher/payloads"; import ErrorDialog, { extractErrorMessageFromError } from "../../../dialogs/ErrorDialog"; -import AccountPhoneNumbers from "../../account/PhoneNumbers"; -import AccountEmailAddresses from "../../account/EmailAddresses"; -import DiscoveryEmailAddresses from "../../discovery/EmailAddresses"; -import DiscoveryPhoneNumbers from "../../discovery/PhoneNumbers"; import ChangePassword from "../../ChangePassword"; -import InlineTermsAgreement from "../../../terms/InlineTermsAgreement"; -import SetIdServer from "../../SetIdServer"; -import SetIntegrationManager from "../../SetIntegrationManager"; -import ToggleSwitch from "../../../elements/ToggleSwitch"; -import { IS_MAC } from "../../../../../Keyboard"; import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection"; -import { SettingsSubsectionHeading } from "../../shared/SettingsSubsectionHeading"; -import Heading from "../../../typography/Heading"; -import InlineSpinner from "../../../elements/InlineSpinner"; -import { ThirdPartyIdentifier } from "../../../../../AddThreepid"; import { SDKContext } from "../../../../../contexts/SDKContext"; import TchapUIFeature from '../../../../../../../../src/tchap/util/TchapUIFeature'; // :TCHAP: hide-discovery-email-phone-settings +import UserPersonalInfoSettings from "../../UserPersonalInfoSettings"; interface IProps { closeSettingsFn: () => void; } interface IState { - language: string; - spellCheckEnabled?: boolean; - spellCheckLanguages: string[]; - haveIdServer: boolean; - idServerHasUnsignedTerms: boolean; - requiredPolicyInfo: - | { - // This object is passed along to a component for handling - hasTerms: false; - policiesAndServices: null; // From the startTermsFlow callback - agreedUrls: null; // From the startTermsFlow callback - resolve: null; // Promise resolve function for startTermsFlow callback - } - | { - hasTerms: boolean; - policiesAndServices: ServicePolicyPair[]; - agreedUrls: string[]; - resolve: (values: string[]) => void; - }; - emails: ThirdPartyIdentifier[]; - msisdns: ThirdPartyIdentifier[]; - loading3pids: boolean; // whether or not the emails and msisdns have been loaded canChangePassword: boolean; idServerName?: string; externalAccountManagementUrl?: string; canMake3pidChanges: boolean; + canSetDisplayName: boolean; + canSetAvatar: boolean; } export default class GeneralUserSettingsTab extends React.Component { public static contextType = SDKContext; public context!: React.ContextType; - private readonly dispatcherRef: string; - public constructor(props: IProps, context: React.ContextType) { super(props); this.context = context; - const cli = this.context.client!; - this.state = { - language: languageHandler.getCurrentLanguage(), - spellCheckEnabled: false, - spellCheckLanguages: [], - haveIdServer: Boolean(cli.getIdentityServerUrl()), - idServerHasUnsignedTerms: false, - requiredPolicyInfo: { - // This object is passed along to a component for handling - hasTerms: false, - policiesAndServices: null, // From the startTermsFlow callback - agreedUrls: null, // From the startTermsFlow callback - resolve: null, // Promise resolve function for startTermsFlow callback - }, - emails: [], - msisdns: [], - loading3pids: true, // whether or not the emails and msisdns have been loaded canChangePassword: false, canMake3pidChanges: false, + canSetDisplayName: false, + canSetAvatar: false, }; - this.dispatcherRef = dis.register(this.onAction); - this.getCapabilities(); - this.getThreepidState(); - } - - public async componentDidMount(): Promise { - const plat = PlatformPeg.get(); - const [spellCheckEnabled, spellCheckLanguages] = await Promise.all([ - plat?.getSpellCheckEnabled(), - plat?.getSpellCheckLanguages(), - ]); - - if (spellCheckLanguages) { - this.setState({ - spellCheckEnabled, - spellCheckLanguages, - }); - } - } - - public componentWillUnmount(): void { - dis.unregister(this.dispatcherRef); } - private onAction = (payload: ActionPayload): void => { - if (payload.action === "id_server_changed") { - this.setState({ haveIdServer: Boolean(this.context.client!.getIdentityServerUrl()) }); - this.getThreepidState(); - } - }; - - private onEmailsChange = (emails: ThirdPartyIdentifier[]): void => { - this.setState({ emails }); - }; - - private onMsisdnsChange = (msisdns: ThirdPartyIdentifier[]): void => { - this.setState({ msisdns }); - }; - private async getCapabilities(): Promise { const cli = this.context.client!; - const capabilities = await cli.getCapabilities(); // this is cached + const capabilities = (await cli.getCapabilities()) ?? {}; const changePasswordCap = capabilities["m.change_password"]; // You can change your password so long as the capability isn't explicitly disabled. The implicit @@ -183,98 +85,19 @@ export default class GeneralUserSettingsTab extends React.Component { - const cli = this.context.client!; - - // Check to see if terms need accepting - this.checkTerms(); + const canSetDisplayName = + !capabilities["m.set_displayname"] || capabilities["m.set_displayname"].enabled === true; + const canSetAvatar = !capabilities["m.set_avatar_url"] || capabilities["m.set_avatar_url"].enabled === true; - // Need to get 3PIDs generally for Account section and possibly also for - // Discovery (assuming we have an IS and terms are agreed). - let threepids: IThreepid[] = []; - try { - threepids = await getThreepidsWithBindStatus(cli); - } catch (e) { - const idServerUrl = cli.getIdentityServerUrl(); - logger.warn( - `Unable to reach identity server at ${idServerUrl} to check ` + `for 3PIDs bindings in Settings`, - ); - logger.warn(e); - } this.setState({ - emails: threepids.filter((a) => a.medium === ThreepidMedium.Email), - msisdns: threepids.filter((a) => a.medium === ThreepidMedium.Phone), - loading3pids: false, + canChangePassword, + externalAccountManagementUrl, + canMake3pidChanges, + canSetDisplayName, + canSetAvatar, }); } - private async checkTerms(): Promise { - // By starting the terms flow we get the logic for checking which terms the user has signed - // for free. So we might as well use that for our own purposes. - const idServerUrl = this.context.client!.getIdentityServerUrl(); - if (!this.state.haveIdServer || !idServerUrl) { - this.setState({ idServerHasUnsignedTerms: false }); - return; - } - - const authClient = new IdentityAuthClient(); - try { - const idAccessToken = await authClient.getAccessToken({ check: false }); - await startTermsFlow( - this.context.client!, - [new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)], - (policiesAndServices, agreedUrls, extraClassNames) => { - return new Promise((resolve, reject) => { - this.setState({ - idServerName: abbreviateUrl(idServerUrl), - requiredPolicyInfo: { - hasTerms: true, - policiesAndServices, - agreedUrls, - resolve, - }, - }); - }); - }, - ); - // User accepted all terms - this.setState({ - requiredPolicyInfo: { - ...this.state.requiredPolicyInfo, // set first so we can override - hasTerms: false, - }, - }); - } catch (e) { - logger.warn(`Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`); - logger.warn(e); - } - } - - private onLanguageChange = (newLanguage: string): void => { - if (this.state.language === newLanguage) return; - - SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage); - this.setState({ language: newLanguage }); - const platform = PlatformPeg.get(); - if (platform) { - platform.setLanguage([newLanguage]); - platform.reload(); - } - }; - - private onSpellCheckLanguagesChange = (languages: string[]): void => { - this.setState({ spellCheckLanguages: languages }); - PlatformPeg.get()?.setSpellCheckLanguages(languages); - }; - - private onSpellCheckEnabledChange = (spellCheckEnabled: boolean): void => { - this.setState({ spellCheckEnabled }); - PlatformPeg.get()?.setSpellCheckEnabled(spellCheckEnabled); - }; - private onPasswordChangeError = (err: Error): void => { logger.error("Failed to change password: " + err); @@ -324,55 +147,16 @@ export default class GeneralUserSettingsTab extends React.Component - ) : ( - - ); - const msisdns = this.state.loading3pids ? ( - - ) : ( - - ); - threepidSection = ( - <> - - {emails} - + private renderAccountSection(): JSX.Element | undefined { + if (!this.state.canChangePassword) return undefined; - - {msisdns} - - - ); - } - - let passwordChangeSection: ReactNode = null; - if (this.state.canChangePassword) { - passwordChangeSection = ( - <> + return ( + <> + {_t("settings|general|password_change_section")} - - ); - } - - let externalAccountManagement: JSX.Element | undefined; - if (this.state.externalAccountManagementUrl) { - const { hostname } = new URL(this.state.externalAccountManagementUrl); - - externalAccountManagement = ( - <> - - {_t( - "settings|general|external_account_management", - { hostname }, - { code: (sub) => {sub} }, - )} - - - {_t("settings|general|oidc_manage_button")} - - - ); - } - return ( - <> - - {externalAccountManagement} - {passwordChangeSection} - {threepidSection} - - ); - } - - private renderLanguageSection(): JSX.Element { - // TODO: Convert to new-styled Field - return ( - - - - ); - } - - private renderSpellCheckSection(): JSX.Element { - const heading = ( - - - - ); - return ( - - {this.state.spellCheckEnabled && !IS_MAC && ( - - )} - - ); - } - - private renderDiscoverySection(): JSX.Element { - if (this.state.requiredPolicyInfo.hasTerms) { - const intro = ( - - {_t("settings|general|discovery_needs_terms", { serverName: this.state.idServerName })} - - ); - return ( - <> - - {/* has its own heading as it includes the current identity server */} - - - ); - } - - const threepidSection = this.state.haveIdServer ? ( - <> - - - - ) : null; - - return ( - <> - {threepidSection} - {/* has its own heading as it includes the current identity server */} - ); } @@ -520,58 +187,24 @@ export default class GeneralUserSettingsTab extends React.Component; - } - public render(): React.ReactNode { - const plaf = PlatformPeg.get(); - const supportsMultiLanguageSpellCheck = plaf?.supportsSpellCheckSettings(); - let accountManagementSection: JSX.Element | undefined; const isAccountManagedExternally = !!this.state.externalAccountManagementUrl; if (SettingsStore.getValue(UIFeature.Deactivate) && !isAccountManagedExternally) { accountManagementSection = this.renderManagementSection(); } - let discoverySection; - // :TCHAP: no need for users to edit the discovery section (TchapUIFeature.showEmailPhoneDiscoverySettings) - if (TchapUIFeature.showEmailPhoneDiscoverySettings && SettingsStore.getValue(UIFeature.IdentityServer)) { - const discoWarning = this.state.requiredPolicyInfo.hasTerms ? ( - - ) : null; - const heading = ( - - {discoWarning} - {_t("settings|general|discovery_section")} - - ); - discoverySection = ( - - {this.renderDiscoverySection()} - - ); - } - return ( - + + {this.renderAccountSection()} - {this.renderLanguageSection()} - {supportsMultiLanguageSpellCheck ? this.renderSpellCheckSection() : null} - {discoverySection} - {this.renderIntegrationManagerSection()} {accountManagementSection} ); diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 6df2a1a03..439cc2122 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -15,9 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { useCallback, useEffect, useState } from "react"; -import { _t } from "../../../../../languageHandler"; +import { _t, getCurrentLanguage } from "../../../../../languageHandler"; import { UseCase } from "../../../../../settings/enums/UseCase"; import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; @@ -33,6 +33,11 @@ import { showUserOnboardingPage } from "../../../user-onboarding/UserOnboardingP import SettingsSubsection from "../../shared/SettingsSubsection"; import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; +import LanguageDropdown from "../../../elements/LanguageDropdown"; +import PlatformPeg from "../../../../../PlatformPeg"; +import { IS_MAC } from "../../../../../Keyboard"; +import SpellCheckSettings from "../../SpellCheckSettings"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; interface IProps { closeSettingsFn(success: boolean): void; @@ -44,6 +49,79 @@ interface IState { readMarkerOutOfViewThresholdMs: string; } +const LanguageSection: React.FC = () => { + const [language, setLanguage] = useState(getCurrentLanguage()); + + const onLanguageChange = useCallback( + (newLanguage: string) => { + if (language === newLanguage) return; + + SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage); + setLanguage(newLanguage); + const platform = PlatformPeg.get(); + if (platform) { + platform.setLanguage([newLanguage]); + platform.reload(); + } + }, + [language], + ); + + return ( +
+ {_t("settings|general|application_language")} + +
+ {_t("settings|general|application_language_reload_hint")} +
+
+ ); +}; + +const SpellCheckSection: React.FC = () => { + const [spellCheckEnabled, setSpellCheckEnabled] = useState(); + const [spellCheckLanguages, setSpellCheckLanguages] = useState(); + + useEffect(() => { + (async () => { + const plaf = PlatformPeg.get(); + const [enabled, langs] = await Promise.all([plaf?.getSpellCheckEnabled(), plaf?.getSpellCheckLanguages()]); + + setSpellCheckEnabled(enabled); + setSpellCheckLanguages(langs || undefined); + })(); + }, []); + + const onSpellCheckEnabledChange = useCallback((enabled: boolean) => { + setSpellCheckEnabled(enabled); + PlatformPeg.get()?.setSpellCheckEnabled(enabled); + }, []); + + const onSpellCheckLanguagesChange = useCallback((languages: string[]): void => { + setSpellCheckLanguages(languages); + PlatformPeg.get()?.setSpellCheckLanguages(languages); + }, []); + + if (!PlatformPeg.get()?.supportsSpellCheckSettings()) return null; + + return ( + <> + + {spellCheckEnabled && spellCheckLanguages !== undefined && !IS_MAC && ( + + )} + + ); +}; + export default class PreferencesUserSettingsTab extends React.Component { private static ROOM_LIST_SETTINGS = ["breadcrumbs", "FTUE.userOnboardingButton"]; @@ -146,6 +224,12 @@ export default class PreferencesUserSettingsTab extends React.Component + {/* The heading string is still 'general' from where it was moved, but this section should become 'general' */} + + + + + {roomListSettings.length > 0 && ( {this.renderGroup(roomListSettings)} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index 47b046c5f..94e1b11ea 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -28,7 +28,6 @@ import { SettingLevel } from "../../../../../settings/SettingLevel"; import SecureBackupPanel from "../../SecureBackupPanel"; import SettingsStore from "../../../../../settings/SettingsStore"; import { UIFeature } from "../../../../../settings/UIFeature"; -import E2eAdvancedPanel, { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel"; import { ActionPayload } from "../../../../../dispatcher/payloads"; import CryptographyPanel from "../../CryptographyPanel"; import SettingsFlag from "../../../elements/SettingsFlag"; @@ -43,6 +42,8 @@ import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection"; import { useOwnDevices } from "../../devices/useOwnDevices"; +import DiscoverySettings from "../../discovery/DiscoverySettings"; +import SetIntegrationManager from "../../SetIntegrationManager"; interface IIgnoredUserProps { userId: string; @@ -345,6 +346,7 @@ export default class SecurityUserSettingsTab extends React.Component + : null; // only show the section if there's something to show - if (ignoreUsersPanel || invitesPanel || e2ePanel) { + if (ignoreUsersPanel || invitesPanel) { advancedSection = ( {ignoreUsersPanel} {invitesPanel} - {e2ePanel} ); } @@ -385,6 +385,7 @@ export default class SecurityUserSettingsTab extends React.Component {crossSigning} diff --git a/src/components/views/spaces/QuickThemeSwitcher.tsx b/src/components/views/spaces/QuickThemeSwitcher.tsx index 66f3d2c04..1a6ee738b 100644 --- a/src/components/views/spaces/QuickThemeSwitcher.tsx +++ b/src/components/views/spaces/QuickThemeSwitcher.tsx @@ -20,13 +20,13 @@ import { _t } from "../../../languageHandler"; import { Action } from "../../../dispatcher/actions"; import { findNonHighContrastTheme, getOrderedThemes } from "../../../theme"; import Dropdown from "../elements/Dropdown"; -import ThemeChoicePanel from "../settings/ThemeChoicePanel"; import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import dis from "../../../dispatcher/dispatcher"; import { RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload"; import PosthogTrackers from "../../../PosthogTrackers"; import { NonEmptyArray } from "../../../@types/common"; +import { useTheme } from "../../../hooks/useTheme"; type Props = { requestClose: () => void; @@ -37,10 +37,10 @@ const MATCH_SYSTEM_THEME_ID = "MATCH_SYSTEM_THEME_ID"; const QuickThemeSwitcher: React.FC = ({ requestClose }) => { const orderedThemes = useMemo(getOrderedThemes, []); - const themeState = ThemeChoicePanel.calculateThemeState(); + const themeState = useTheme(); const nonHighContrast = findNonHighContrastTheme(themeState.theme); const theme = nonHighContrast ? nonHighContrast : themeState.theme; - const { useSystemTheme } = themeState; + const { systemThemeActivated } = themeState; const themeOptions = [ { @@ -50,7 +50,7 @@ const QuickThemeSwitcher: React.FC = ({ requestClose }) => { ...orderedThemes, ]; - const selectedTheme = useSystemTheme ? MATCH_SYSTEM_THEME_ID : theme; + const selectedTheme = systemThemeActivated ? MATCH_SYSTEM_THEME_ID : theme; const onOptionChange = async (newTheme: string): Promise => { PosthogTrackers.trackInteraction("WebQuickSettingsThemeDropdown"); diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index a690d3494..33d62771d 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -218,7 +218,8 @@ const SpaceCreateMenu: React.FC<{ onFinished(): void; }> = ({ onFinished }) => { const cli = useMatrixClientContext(); - const [visibility, setVisibility] = useState(null); + // const [visibility, setVisibility] = useState(null); + const [visibility, setVisibility] = useState(Visibility.Private); // :TCHAP: space-remove-public-and-subspace const [busy, setBusy] = useState(false); const [name, setName] = useState(""); @@ -280,71 +281,111 @@ const SpaceCreateMenu: React.FC<{ }; let body; - if (visibility === null) { - body = ( - -

{_t("create_space|label")}

-

{_t("create_space|explainer")}

- - setVisibility(Visibility.Public)} - /> - setVisibility(Visibility.Private)} - /> - - {supportsSpaceFiltering && ( - - {_t("create_space|search_public_button")} - - )} -
- ); - } else { - body = ( - - setVisibility(null)} - title={_t("action|go_back")} - /> - -

- {visibility === Visibility.Public - ? _t("create_space|public_heading") - : _t("create_space|private_heading")} -

-

- {_t("create_space|add_details_prompt")} {_t("create_space|add_details_prompt_2")} -

- - + // :TCHAP: space-remove-public-and-subspace + // if (visibility === null) { + // body = ( + // + //

{_t("create_space|label")}

+ //

{_t("create_space|explainer")}

+ + // setVisibility(Visibility.Public)} + // /> + // setVisibility(Visibility.Private)} + // /> + + // {supportsSpaceFiltering && ( + // + // {_t("create_space|search_public_button")} + // + // )} + //
+ // ); + // } else { + // body = ( + // + // setVisibility(null)} + // title={_t("action|go_back")} + // /> + + //

+ // {visibility === Visibility.Public + // ? _t("create_space|public_heading") + // : _t("create_space|private_heading")} + //

+ //

+ // {_t("create_space|add_details_prompt")} {_t("create_space|add_details_prompt_2")} + //

+ + // + + // + // {busy ? _t("create_space|creating") : _t("action|create")} + // + //
+ // ); + // } + body = ( + +

+ {_t("create_space|private_heading")} +

+

+ {_t("create_space|add_details_prompt")} {_t("create_space|add_details_prompt_2", {}, { + a: (sub) => ( + { + window.open("https://aide.tchap.beta.gouv.fr/fr/article/comment-creer-un-espace-sur-tchap-1wmlenx","_blank") + }}> + {sub} + + ), + }) + } +

+ + - - {busy ? _t("create_space|creating") : _t("action|create")} - -
- ); - } + + {busy ? _t("create_space|creating") : _t("action|create")} + +
+ ); + // end :TCHAP: return ( { @@ -385,7 +389,22 @@ const SpacePanel: React.FC = () => { >