mirror of
https://github.com/element-hq/element-web
synced 2024-11-21 16:55:34 +03:00
Apply prettier formatting
This commit is contained in:
parent
1cac306093
commit
526645c791
1576 changed files with 65385 additions and 62478 deletions
96
.eslintrc.js
96
.eslintrc.js
|
@ -1,12 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: ["matrix-org"],
|
||||||
"matrix-org",
|
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
|
||||||
],
|
|
||||||
extends: [
|
|
||||||
"plugin:matrix-org/babel",
|
|
||||||
"plugin:matrix-org/react",
|
|
||||||
"plugin:matrix-org/a11y",
|
|
||||||
],
|
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
node: true,
|
node: true,
|
||||||
|
@ -40,34 +34,47 @@ module.exports = {
|
||||||
],
|
],
|
||||||
|
|
||||||
// Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell.
|
// Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell.
|
||||||
"no-restricted-imports": ["error", {
|
"no-restricted-imports": [
|
||||||
"paths": [{
|
"error",
|
||||||
"name": "matrix-js-sdk",
|
{
|
||||||
"message": "Please use matrix-js-sdk/src/matrix instead",
|
paths: [
|
||||||
}, {
|
{
|
||||||
"name": "matrix-js-sdk/",
|
name: "matrix-js-sdk",
|
||||||
"message": "Please use matrix-js-sdk/src/matrix instead",
|
message: "Please use matrix-js-sdk/src/matrix instead",
|
||||||
}, {
|
},
|
||||||
"name": "matrix-js-sdk/src",
|
{
|
||||||
"message": "Please use matrix-js-sdk/src/matrix instead",
|
name: "matrix-js-sdk/",
|
||||||
}, {
|
message: "Please use matrix-js-sdk/src/matrix instead",
|
||||||
"name": "matrix-js-sdk/src/",
|
},
|
||||||
"message": "Please use matrix-js-sdk/src/matrix instead",
|
{
|
||||||
}, {
|
name: "matrix-js-sdk/src",
|
||||||
"name": "matrix-js-sdk/src/index",
|
message: "Please use matrix-js-sdk/src/matrix instead",
|
||||||
"message": "Please use matrix-js-sdk/src/matrix instead",
|
},
|
||||||
}, {
|
{
|
||||||
"name": "matrix-react-sdk",
|
name: "matrix-js-sdk/src/",
|
||||||
"message": "Please use matrix-react-sdk/src/index instead",
|
message: "Please use matrix-js-sdk/src/matrix instead",
|
||||||
}, {
|
},
|
||||||
"name": "matrix-react-sdk/",
|
{
|
||||||
"message": "Please use matrix-react-sdk/src/index instead",
|
name: "matrix-js-sdk/src/index",
|
||||||
}],
|
message: "Please use matrix-js-sdk/src/matrix instead",
|
||||||
"patterns": [{
|
},
|
||||||
"group": ["matrix-js-sdk/lib", "matrix-js-sdk/lib/", "matrix-js-sdk/lib/**"],
|
{
|
||||||
"message": "Please use matrix-js-sdk/src/* instead",
|
name: "matrix-react-sdk",
|
||||||
}],
|
message: "Please use matrix-react-sdk/src/index instead",
|
||||||
}],
|
},
|
||||||
|
{
|
||||||
|
name: "matrix-react-sdk/",
|
||||||
|
message: "Please use matrix-react-sdk/src/index instead",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
group: ["matrix-js-sdk/lib", "matrix-js-sdk/lib/", "matrix-js-sdk/lib/**"],
|
||||||
|
message: "Please use matrix-js-sdk/src/* instead",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
// There are too many a11y violations to fix at once
|
// There are too many a11y violations to fix at once
|
||||||
// Turn violated rules off until they are fixed
|
// Turn violated rules off until they are fixed
|
||||||
|
@ -90,15 +97,8 @@ module.exports = {
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: [
|
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}", "cypress/**/*.ts"],
|
||||||
"src/**/*.{ts,tsx}",
|
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
|
||||||
"test/**/*.{ts,tsx}",
|
|
||||||
"cypress/**/*.ts",
|
|
||||||
],
|
|
||||||
extends: [
|
|
||||||
"plugin:matrix-org/typescript",
|
|
||||||
"plugin:matrix-org/react",
|
|
||||||
],
|
|
||||||
rules: {
|
rules: {
|
||||||
// temporary disabled
|
// temporary disabled
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
@ -151,12 +151,12 @@ module.exports = {
|
||||||
"src/components/views/rooms/MessageComposer.tsx",
|
"src/components/views/rooms/MessageComposer.tsx",
|
||||||
"src/components/views/rooms/ReplyPreview.tsx",
|
"src/components/views/rooms/ReplyPreview.tsx",
|
||||||
"src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx",
|
"src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx",
|
||||||
"src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx"
|
"src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx",
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
|
@ -166,7 +166,7 @@ module.exports = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildRestrictedPropertiesOptions(properties, message) {
|
function buildRestrictedPropertiesOptions(properties, message) {
|
||||||
return properties.map(prop => {
|
return properties.map((prop) => {
|
||||||
let [object, property] = prop.split(".");
|
let [object, property] = prop.split(".");
|
||||||
if (object === "*") {
|
if (object === "*") {
|
||||||
object = undefined;
|
object = undefined;
|
||||||
|
|
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
* [ ] Tests written for new code (and old code if feasible)
|
- [ ] Tests written for new code (and old code if feasible)
|
||||||
* [ ] Linter and other CI checks pass
|
- [ ] Linter and other CI checks pass
|
||||||
* [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/matrix-org/matrix-react-sdk/blob/develop/CONTRIBUTING.md))
|
- [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/matrix-org/matrix-react-sdk/blob/develop/CONTRIBUTING.md))
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
If you would like to specify text for the changelog entry other than your PR title, add the following:
|
If you would like to specify text for the changelog entry other than your PR title, add the following:
|
||||||
|
|
4
.github/renovate.json
vendored
4
.github/renovate.json
vendored
|
@ -1,6 +1,4 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": ["github>matrix-org/renovate-config-element-web"]
|
||||||
"github>matrix-org/renovate-config-element-web"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
13
.github/workflows/cypress.yaml
vendored
13
.github/workflows/cypress.yaml
vendored
|
@ -75,7 +75,8 @@ jobs:
|
||||||
actions: read
|
actions: read
|
||||||
issues: read
|
issues: read
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
environment: Cypress
|
environment:
|
||||||
|
Cypress
|
||||||
#strategy:
|
#strategy:
|
||||||
# fail-fast: false
|
# fail-fast: false
|
||||||
# matrix:
|
# matrix:
|
||||||
|
@ -107,11 +108,12 @@ jobs:
|
||||||
# to run the tests, so use chrome.
|
# to run the tests, so use chrome.
|
||||||
browser: chrome
|
browser: chrome
|
||||||
start: npx serve -p 8080 webapp
|
start: npx serve -p 8080 webapp
|
||||||
wait-on: 'http://localhost:8080'
|
wait-on: "http://localhost:8080"
|
||||||
record: true
|
record:
|
||||||
|
true
|
||||||
#parallel: true
|
#parallel: true
|
||||||
#command-prefix: 'yarn percy exec --parallel --'
|
#command-prefix: 'yarn percy exec --parallel --'
|
||||||
command-prefix: 'yarn percy exec --'
|
command-prefix: "yarn percy exec --"
|
||||||
ci-build-id: ${{ needs.prepare.outputs.uuid }}
|
ci-build-id: ${{ needs.prepare.outputs.uuid }}
|
||||||
env:
|
env:
|
||||||
# pass the Dashboard record key as an environment variable
|
# pass the Dashboard record key as an environment variable
|
||||||
|
@ -141,7 +143,8 @@ jobs:
|
||||||
# tell Percy more details about the context of this run
|
# tell Percy more details about the context of this run
|
||||||
PERCY_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
PERCY_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||||
PERCY_COMMIT: ${{ github.event.workflow_run.head_sha }}
|
PERCY_COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||||
PERCY_PULL_REQUEST: ${{ needs.prepare.outputs.pr_id }}
|
PERCY_PULL_REQUEST:
|
||||||
|
${{ needs.prepare.outputs.pr_id }}
|
||||||
#PERCY_PARALLEL_TOTAL: ${{ strategy.job-total }}
|
#PERCY_PARALLEL_TOTAL: ${{ strategy.job-total }}
|
||||||
PERCY_PARALLEL_NONCE: ${{ needs.prepare.outputs.uuid }}
|
PERCY_PARALLEL_NONCE: ${{ needs.prepare.outputs.uuid }}
|
||||||
|
|
||||||
|
|
2
.github/workflows/element-web.yaml
vendored
2
.github/workflows/element-web.yaml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
- name: Fetch layered build
|
- name: Fetch layered build
|
||||||
id: layered_build
|
id: layered_build
|
||||||
|
|
2
.github/workflows/i18n_check.yml
vendored
2
.github/workflows/i18n_check.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
# Does not need branch matching as only analyses this layer
|
# Does not need branch matching as only analyses this layer
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
|
|
12
.github/workflows/static_analysis.yaml
vendored
12
.github/workflows/static_analysis.yaml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
run: "./scripts/ci/install-deps.sh --ignore-scripts"
|
run: "./scripts/ci/install-deps.sh --ignore-scripts"
|
||||||
|
@ -48,8 +48,8 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
args:
|
args:
|
||||||
- '--strict --noImplicitAny'
|
- "--strict --noImplicitAny"
|
||||||
- '--noImplicitAny'
|
- "--noImplicitAny"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
# Does not need branch matching as only analyses this layer
|
# Does not need branch matching as only analyses this layer
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
|
@ -120,7 +120,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
# Does not need branch matching as only analyses this layer
|
# Does not need branch matching as only analyses this layer
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
|
@ -137,7 +137,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
run: "scripts/ci/layered.sh"
|
run: "scripts/ci/layered.sh"
|
||||||
|
|
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
- name: Yarn cache
|
- name: Yarn cache
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
run: "./scripts/ci/install-deps.sh --ignore-scripts"
|
run: "./scripts/ci/install-deps.sh --ignore-scripts"
|
||||||
|
@ -53,7 +53,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: "./scripts/ci/app-tests.sh"
|
run: "./scripts/ci/app-tests.sh"
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"extends": [
|
extends: ["stylelint-config-standard", "stylelint-config-prettier"],
|
||||||
"stylelint-config-standard",
|
customSyntax: require("postcss-scss"),
|
||||||
"stylelint-config-prettier",
|
plugins: ["stylelint-scss"],
|
||||||
],
|
rules: {
|
||||||
customSyntax: require('postcss-scss'),
|
|
||||||
"plugins": [
|
|
||||||
"stylelint-scss",
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"color-hex-case": null,
|
"color-hex-case": null,
|
||||||
"comment-empty-line-before": null,
|
"comment-empty-line-before": null,
|
||||||
"declaration-empty-line-before": null,
|
"declaration-empty-line-before": null,
|
||||||
|
@ -22,15 +17,18 @@ module.exports = {
|
||||||
"at-rule-no-unknown": null,
|
"at-rule-no-unknown": null,
|
||||||
"no-descending-specificity": null,
|
"no-descending-specificity": null,
|
||||||
"no-empty-first-line": true,
|
"no-empty-first-line": true,
|
||||||
"scss/at-rule-no-unknown": [true, {
|
"scss/at-rule-no-unknown": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
// https://github.com/vector-im/element-web/issues/10544
|
// https://github.com/vector-im/element-web/issues/10544
|
||||||
"ignoreAtRules": ["define-mixin"],
|
ignoreAtRules: ["define-mixin"],
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
// Disable `&_kind`-style selectors while our unused CSS approach is "Find & Replace All"
|
// Disable `&_kind`-style selectors while our unused CSS approach is "Find & Replace All"
|
||||||
// rather than a CI thing. Shorthand selectors are harder to detect when searching for a
|
// rather than a CI thing. Shorthand selectors are harder to detect when searching for a
|
||||||
// class name. This regex is trying to *allow* anything except `&words`, such as `&::before`,
|
// class name. This regex is trying to *allow* anything except `&words`, such as `&::before`,
|
||||||
// `&.mx_Class`, etc.
|
// `&.mx_Class`, etc.
|
||||||
"selector-nested-pattern": "^((&[ :.\\\[,])|([^&]))",
|
"selector-nested-pattern": "^((&[ :.\\[,])|([^&]))",
|
||||||
"declaration-colon-space-after": "always-single-line",
|
"declaration-colon-space-after": "always-single-line",
|
||||||
// Disable some defaults
|
// Disable some defaults
|
||||||
"selector-class-pattern": null,
|
"selector-class-pattern": null,
|
||||||
|
@ -52,4 +50,4 @@ module.exports = {
|
||||||
"number-max-precision": null,
|
"number-max-precision": null,
|
||||||
"no-invalid-double-slash-comments": true,
|
"no-invalid-double-slash-comments": true,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
16848
CHANGELOG.md
16848
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,3 @@
|
||||||
Contributing code to matrix-react-sdk
|
# Contributing code to matrix-react-sdk
|
||||||
=====================================
|
|
||||||
|
|
||||||
matrix-react-sdk follows the same pattern as https://github.com/vector-im/element-web/blob/develop/CONTRIBUTING.md
|
matrix-react-sdk follows the same pattern as https://github.com/vector-im/element-web/blob/develop/CONTRIBUTING.md
|
||||||
|
|
75
README.md
75
README.md
|
@ -9,18 +9,18 @@
|
||||||
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk)
|
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk)
|
||||||
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=bugs)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk)
|
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=bugs)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk)
|
||||||
|
|
||||||
matrix-react-sdk
|
# matrix-react-sdk
|
||||||
================
|
|
||||||
|
|
||||||
This is a react-based SDK for inserting a Matrix chat/voip client into a web page.
|
This is a react-based SDK for inserting a Matrix chat/voip client into a web page.
|
||||||
|
|
||||||
This package provides the React components needed to build a Matrix web client
|
This package provides the React components needed to build a Matrix web client
|
||||||
using React. It is not useable in isolation, and instead must be used from
|
using React. It is not useable in isolation, and instead must be used from
|
||||||
a 'skin'. A skin provides:
|
a 'skin'. A skin provides:
|
||||||
* Customised implementations of presentation components.
|
|
||||||
* Custom CSS
|
- Customised implementations of presentation components.
|
||||||
* The containing application
|
- Custom CSS
|
||||||
* Zero or more 'modules' containing non-UI functionality
|
- The containing application
|
||||||
|
- Zero or more 'modules' containing non-UI functionality
|
||||||
|
|
||||||
As of Aug 2018, the only skin that exists is
|
As of Aug 2018, the only skin that exists is
|
||||||
[`vector-im/element-web`](https://github.com/vector-im/element-web/); it and
|
[`vector-im/element-web`](https://github.com/vector-im/element-web/); it and
|
||||||
|
@ -28,17 +28,17 @@ As of Aug 2018, the only skin that exists is
|
||||||
be considered as a single project (for instance, matrix-react-sdk bugs
|
be considered as a single project (for instance, matrix-react-sdk bugs
|
||||||
are currently filed against vector-im/element-web rather than this project).
|
are currently filed against vector-im/element-web rather than this project).
|
||||||
|
|
||||||
Translation Status
|
## Translation Status
|
||||||
------------------
|
|
||||||
[![Translation status](https://translate.element.io/widgets/element-web/-/multi-auto.svg)](https://translate.element.io/engage/element-web/?utm_source=widget)
|
[![Translation status](https://translate.element.io/widgets/element-web/-/multi-auto.svg)](https://translate.element.io/engage/element-web/?utm_source=widget)
|
||||||
|
|
||||||
Developer Guide
|
## Developer Guide
|
||||||
---------------
|
|
||||||
|
|
||||||
Platform Targets:
|
Platform Targets:
|
||||||
* Chrome, Firefox and Safari.
|
|
||||||
* WebRTC features (VoIP and Video calling) are only available in Chrome & Firefox.
|
- Chrome, Firefox and Safari.
|
||||||
* Mobile Web is not currently a target platform - instead please use the native
|
- WebRTC features (VoIP and Video calling) are only available in Chrome & Firefox.
|
||||||
|
- Mobile Web is not currently a target platform - instead please use the native
|
||||||
iOS (https://github.com/matrix-org/matrix-ios-kit) and Android
|
iOS (https://github.com/matrix-org/matrix-ios-kit) and Android
|
||||||
(https://github.com/matrix-org/matrix-android-sdk2) SDKs.
|
(https://github.com/matrix-org/matrix-android-sdk2) SDKs.
|
||||||
|
|
||||||
|
@ -52,16 +52,17 @@ Our code style is also the same as Element's:
|
||||||
https://github.com/vector-im/element-web/blob/develop/code_style.md
|
https://github.com/vector-im/element-web/blob/develop/code_style.md
|
||||||
|
|
||||||
Code should be committed as follows:
|
Code should be committed as follows:
|
||||||
* All new components:
|
|
||||||
|
- All new components:
|
||||||
https://github.com/matrix-org/matrix-react-sdk/tree/master/src/components
|
https://github.com/matrix-org/matrix-react-sdk/tree/master/src/components
|
||||||
* Element-specific components:
|
- Element-specific components:
|
||||||
https://github.com/vector-im/element-web/tree/master/src/components
|
https://github.com/vector-im/element-web/tree/master/src/components
|
||||||
* In practice, `matrix-react-sdk` is still evolving so fast that the
|
- In practice, `matrix-react-sdk` is still evolving so fast that the
|
||||||
maintenance burden of customising and overriding these components for
|
maintenance burden of customising and overriding these components for
|
||||||
Element can seriously impede development. So right now, there should be
|
Element can seriously impede development. So right now, there should be
|
||||||
very few (if any) customisations for Element.
|
very few (if any) customisations for Element.
|
||||||
* CSS: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css
|
- CSS: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css
|
||||||
* Theme specific CSS & resources:
|
- Theme specific CSS & resources:
|
||||||
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes
|
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes
|
||||||
|
|
||||||
React components in matrix-react-sdk come in two different flavours:
|
React components in matrix-react-sdk come in two different flavours:
|
||||||
|
@ -75,55 +76,55 @@ visual rendering via props.
|
||||||
Good separation between the components is maintained by adopting various best
|
Good separation between the components is maintained by adopting various best
|
||||||
practices that anyone working with the SDK needs to be aware of and uphold:
|
practices that anyone working with the SDK needs to be aware of and uphold:
|
||||||
|
|
||||||
* Components are named with upper camel case (e.g. views/rooms/EventTile.js)
|
- Components are named with upper camel case (e.g. views/rooms/EventTile.js)
|
||||||
|
|
||||||
* They are organised in a typically two-level hierarchy - first whether the
|
- They are organised in a typically two-level hierarchy - first whether the
|
||||||
component is a view or a structure, and then a broad functional grouping
|
component is a view or a structure, and then a broad functional grouping
|
||||||
(e.g. 'rooms' here)
|
(e.g. 'rooms' here)
|
||||||
|
|
||||||
* The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css).
|
- The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css).
|
||||||
CSS for matrix-react-sdk currently resides in
|
CSS for matrix-react-sdk currently resides in
|
||||||
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css.
|
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css.
|
||||||
|
|
||||||
* Per-view CSS is optional - it could choose to inherit all its styling from
|
- Per-view CSS is optional - it could choose to inherit all its styling from
|
||||||
the context of the rest of the app, although this is unusual for any but
|
the context of the rest of the app, although this is unusual for any but
|
||||||
* Theme specific CSS & resources:
|
- Theme specific CSS & resources:
|
||||||
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes
|
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes
|
||||||
structural components (lacking presentation logic) and the simplest view
|
structural components (lacking presentation logic) and the simplest view
|
||||||
components.
|
components.
|
||||||
|
|
||||||
* The view MUST *only* refer to the CSS rules defined in its own CSS file.
|
- The view MUST _only_ refer to the CSS rules defined in its own CSS file.
|
||||||
'Stealing' styling information from other components (including parents)
|
'Stealing' styling information from other components (including parents)
|
||||||
is not cool, as it breaks the independence of the components.
|
is not cool, as it breaks the independence of the components.
|
||||||
|
|
||||||
* CSS classes are named with an app-specific name-spacing prefix to try to
|
- CSS classes are named with an app-specific name-spacing prefix to try to
|
||||||
avoid CSS collisions. The base skin shipped by Matrix.org with the
|
avoid CSS collisions. The base skin shipped by Matrix.org with the
|
||||||
matrix-react-sdk uses the naming prefix "mx_". A company called Yoyodyne
|
matrix-react-sdk uses the naming prefix "mx*". A company called Yoyodyne
|
||||||
Inc might use a prefix like "yy_" for its app-specific classes.
|
Inc might use a prefix like "yy*" for its app-specific classes.
|
||||||
|
|
||||||
* CSS classes use upper camel case when they describe React components - e.g.
|
- CSS classes use upper camel case when they describe React components - e.g.
|
||||||
.mx_MessageTile is the selector for the CSS applied to a MessageTile view.
|
.mx_MessageTile is the selector for the CSS applied to a MessageTile view.
|
||||||
|
|
||||||
* CSS classes for DOM elements within a view which aren't components are named
|
- CSS classes for DOM elements within a view which aren't components are named
|
||||||
by appending a lower camel case identifier to the view's class name - e.g.
|
by appending a lower camel case identifier to the view's class name - e.g.
|
||||||
.mx_MessageTile_randomDiv is how you'd name the class of an arbitrary div
|
.mx_MessageTile_randomDiv is how you'd name the class of an arbitrary div
|
||||||
within the MessageTile view.
|
within the MessageTile view.
|
||||||
|
|
||||||
* We deliberately use vanilla CSS 3.0 to avoid adding any more magic
|
- We deliberately use vanilla CSS 3.0 to avoid adding any more magic
|
||||||
dependencies into the mix than we already have. App developers are welcome
|
dependencies into the mix than we already have. App developers are welcome
|
||||||
to use whatever floats their boat however. In future we'll start using
|
to use whatever floats their boat however. In future we'll start using
|
||||||
css-next to pull in features like CSS variable support.
|
css-next to pull in features like CSS variable support.
|
||||||
|
|
||||||
* The CSS for a component can override the rules for child components.
|
- The CSS for a component can override the rules for child components.
|
||||||
For instance, .mx_RoomList .mx_RoomTile {} would be the selector to override
|
For instance, .mx*RoomList .mx_RoomTile {} would be the selector to override
|
||||||
styles of RoomTiles when viewed in the context of a RoomList view.
|
styles of RoomTiles when viewed in the context of a RoomList view.
|
||||||
Overrides *must* be scoped to the View's CSS class - i.e. don't just define
|
Overrides \_must* be scoped to the View's CSS class - i.e. don't just define
|
||||||
.mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its
|
.mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its
|
||||||
own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override
|
own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override
|
||||||
only to the context of RoomList views. N.B. overrides should be relatively
|
only to the context of RoomList views. N.B. overrides should be relatively
|
||||||
rare as in general CSS inheritance should be enough.
|
rare as in general CSS inheritance should be enough.
|
||||||
|
|
||||||
* Components should render only within the bounding box of their outermost DOM
|
- Components should render only within the bounding box of their outermost DOM
|
||||||
element. Page-absolute positioning and negative CSS margins and similar are
|
element. Page-absolute positioning and negative CSS margins and similar are
|
||||||
generally not cool and stop the component from being reused easily in
|
generally not cool and stop the component from being reused easily in
|
||||||
different places.
|
different places.
|
||||||
|
@ -135,14 +136,12 @@ made them harder to find relative to a functional split, and didn't emphasise
|
||||||
the distinction between 'structural' and 'view' components, so we backed away
|
the distinction between 'structural' and 'view' components, so we backed away
|
||||||
from it.
|
from it.
|
||||||
|
|
||||||
Github Issues
|
## Github Issues
|
||||||
-------------
|
|
||||||
|
|
||||||
All issues should be filed under https://github.com/vector-im/element-web/issues
|
All issues should be filed under https://github.com/vector-im/element-web/issues
|
||||||
for now.
|
for now.
|
||||||
|
|
||||||
Development
|
## Development
|
||||||
-----------
|
|
||||||
|
|
||||||
Ensure you have the latest LTS version of Node.js installed.
|
Ensure you have the latest LTS version of Node.js installed.
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const EventEmitter = require("events");
|
const EventEmitter = require("events");
|
||||||
const { LngLat, NavigationControl, LngLatBounds, AttributionControl } = require('maplibre-gl');
|
const { LngLat, NavigationControl, LngLatBounds, AttributionControl } = require("maplibre-gl");
|
||||||
|
|
||||||
class MockMap extends EventEmitter {
|
class MockMap extends EventEmitter {
|
||||||
addControl = jest.fn();
|
addControl = jest.fn();
|
||||||
|
@ -32,7 +32,7 @@ class MockGeolocateControl extends EventEmitter {
|
||||||
trigger = jest.fn();
|
trigger = jest.fn();
|
||||||
}
|
}
|
||||||
const MockGeolocateInstance = new MockGeolocateControl();
|
const MockGeolocateInstance = new MockGeolocateControl();
|
||||||
const MockMarker = {}
|
const MockMarker = {};
|
||||||
MockMarker.setLngLat = jest.fn().mockReturnValue(MockMarker);
|
MockMarker.setLngLat = jest.fn().mockReturnValue(MockMarker);
|
||||||
MockMarker.addTo = jest.fn().mockReturnValue(MockMarker);
|
MockMarker.addTo = jest.fn().mockReturnValue(MockMarker);
|
||||||
MockMarker.remove = jest.fn().mockReturnValue(MockMarker);
|
MockMarker.remove = jest.fn().mockReturnValue(MockMarker);
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export const Icon = 'div';
|
export const Icon = "div";
|
||||||
export default "image-file-stub";
|
export default "image-file-stub";
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"sourceMaps": "inline",
|
sourceMaps: "inline",
|
||||||
"presets": [
|
presets: [
|
||||||
["@babel/preset-env", {
|
[
|
||||||
"targets": [
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
targets: [
|
||||||
"last 2 Chrome versions",
|
"last 2 Chrome versions",
|
||||||
"last 2 Firefox versions",
|
"last 2 Firefox versions",
|
||||||
"last 2 Safari versions",
|
"last 2 Safari versions",
|
||||||
"last 2 Edge versions",
|
"last 2 Edge versions",
|
||||||
],
|
],
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
"@babel/preset-typescript",
|
"@babel/preset-typescript",
|
||||||
"@babel/preset-react",
|
"@babel/preset-react",
|
||||||
],
|
],
|
||||||
"plugins": [
|
plugins: [
|
||||||
"@babel/plugin-proposal-export-default-from",
|
"@babel/plugin-proposal-export-default-from",
|
||||||
"@babel/plugin-proposal-numeric-separator",
|
"@babel/plugin-proposal-numeric-separator",
|
||||||
"@babel/plugin-proposal-class-properties",
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
|
|
@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineConfig } from 'cypress';
|
import { defineConfig } from "cypress";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
videoUploadOnPasses: false,
|
videoUploadOnPasses: false,
|
||||||
projectId: 'ppvnzg',
|
projectId: "ppvnzg",
|
||||||
experimentalInteractiveRunEvents: true,
|
experimentalInteractiveRunEvents: true,
|
||||||
defaultCommandTimeout: 10000,
|
defaultCommandTimeout: 10000,
|
||||||
chromeWebSecurity: false,
|
chromeWebSecurity: false,
|
||||||
e2e: {
|
e2e: {
|
||||||
setupNodeEvents(on, config) {
|
setupNodeEvents(on, config) {
|
||||||
return require('./cypress/plugins/index.ts').default(on, config);
|
return require("./cypress/plugins/index.ts").default(on, config);
|
||||||
},
|
},
|
||||||
baseUrl: 'http://localhost:8080',
|
baseUrl: "http://localhost:8080",
|
||||||
experimentalSessionAndOrigin: true,
|
experimentalSessionAndOrigin: true,
|
||||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
specPattern: "cypress/e2e/**/*.{js,jsx,ts,tsx}",
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
// Docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
|
// Docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe("Composer", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,26 +42,26 @@ describe("Composer", () => {
|
||||||
|
|
||||||
it("sends a message when you click send or press Enter", () => {
|
it("sends a message when you click send or press Enter", () => {
|
||||||
// Type a message
|
// Type a message
|
||||||
cy.get('div[contenteditable=true]').type('my message 0');
|
cy.get("div[contenteditable=true]").type("my message 0");
|
||||||
// It has not been sent yet
|
// It has not been sent yet
|
||||||
cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist');
|
cy.contains(".mx_EventTile_body", "my message 0").should("not.exist");
|
||||||
|
|
||||||
// Click send
|
// Click send
|
||||||
cy.get('div[aria-label="Send message"]').click();
|
cy.get('div[aria-label="Send message"]').click();
|
||||||
// It has been sent
|
// It has been sent
|
||||||
cy.contains('.mx_EventTile_body', 'my message 0');
|
cy.contains(".mx_EventTile_body", "my message 0");
|
||||||
|
|
||||||
// Type another and press Enter afterwards
|
// Type another and press Enter afterwards
|
||||||
cy.get('div[contenteditable=true]').type('my message 1{enter}');
|
cy.get("div[contenteditable=true]").type("my message 1{enter}");
|
||||||
// It was sent
|
// It was sent
|
||||||
cy.contains('.mx_EventTile_body', 'my message 1');
|
cy.contains(".mx_EventTile_body", "my message 1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can write formatted text", () => {
|
it("can write formatted text", () => {
|
||||||
cy.get('div[contenteditable=true]').type('my bold{ctrl+b} message');
|
cy.get("div[contenteditable=true]").type("my bold{ctrl+b} message");
|
||||||
cy.get('div[aria-label="Send message"]').click();
|
cy.get('div[aria-label="Send message"]').click();
|
||||||
// Note: both "bold" and "message" are bold, which is probably surprising
|
// Note: both "bold" and "message" are bold, which is probably surprising
|
||||||
cy.contains('.mx_EventTile_body strong', 'bold message');
|
cy.contains(".mx_EventTile_body strong", "bold message");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow user to input emoji via graphical picker", () => {
|
it("should allow user to input emoji via graphical picker", () => {
|
||||||
|
@ -74,7 +74,7 @@ describe("Composer", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get(".mx_ContextualMenu_background").click(); // Close emoji picker
|
cy.get(".mx_ContextualMenu_background").click(); // Close emoji picker
|
||||||
cy.get('div[contenteditable=true]').type("{enter}"); // Send message
|
cy.get("div[contenteditable=true]").type("{enter}"); // Send message
|
||||||
|
|
||||||
cy.contains(".mx_EventTile_body", "😇");
|
cy.contains(".mx_EventTile_body", "😇");
|
||||||
});
|
});
|
||||||
|
@ -86,14 +86,14 @@ describe("Composer", () => {
|
||||||
|
|
||||||
it("only sends when you press Ctrl+Enter", () => {
|
it("only sends when you press Ctrl+Enter", () => {
|
||||||
// Type a message and press Enter
|
// Type a message and press Enter
|
||||||
cy.get('div[contenteditable=true]').type('my message 3{enter}');
|
cy.get("div[contenteditable=true]").type("my message 3{enter}");
|
||||||
// It has not been sent yet
|
// It has not been sent yet
|
||||||
cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist');
|
cy.contains(".mx_EventTile_body", "my message 3").should("not.exist");
|
||||||
|
|
||||||
// Press Ctrl+Enter
|
// Press Ctrl+Enter
|
||||||
cy.get('div[contenteditable=true]').type('{ctrl+enter}');
|
cy.get("div[contenteditable=true]").type("{ctrl+enter}");
|
||||||
// It was sent
|
// It was sent
|
||||||
cy.contains('.mx_EventTile_body', 'my message 3');
|
cy.contains(".mx_EventTile_body", "my message 3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -109,28 +109,28 @@ describe("Composer", () => {
|
||||||
|
|
||||||
it("sends a message when you click send or press Enter", () => {
|
it("sends a message when you click send or press Enter", () => {
|
||||||
// Type a message
|
// Type a message
|
||||||
cy.get('div[contenteditable=true]').type('my message 0');
|
cy.get("div[contenteditable=true]").type("my message 0");
|
||||||
// It has not been sent yet
|
// It has not been sent yet
|
||||||
cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist');
|
cy.contains(".mx_EventTile_body", "my message 0").should("not.exist");
|
||||||
|
|
||||||
// Click send
|
// Click send
|
||||||
cy.get('div[aria-label="Send message"]').click();
|
cy.get('div[aria-label="Send message"]').click();
|
||||||
// It has been sent
|
// It has been sent
|
||||||
cy.contains('.mx_EventTile_body', 'my message 0');
|
cy.contains(".mx_EventTile_body", "my message 0");
|
||||||
|
|
||||||
// Type another
|
// Type another
|
||||||
cy.get('div[contenteditable=true]').type('my message 1');
|
cy.get("div[contenteditable=true]").type("my message 1");
|
||||||
// Press enter. Would be nice to just use {enter} but we can't because Cypress
|
// Press enter. Would be nice to just use {enter} but we can't because Cypress
|
||||||
// does not trigger an insertParagraph when you do that.
|
// does not trigger an insertParagraph when you do that.
|
||||||
cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" });
|
cy.get("div[contenteditable=true]").trigger("input", { inputType: "insertParagraph" });
|
||||||
// It was sent
|
// It was sent
|
||||||
cy.contains('.mx_EventTile_body', 'my message 1');
|
cy.contains(".mx_EventTile_body", "my message 1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can write formatted text", () => {
|
it("can write formatted text", () => {
|
||||||
cy.get('div[contenteditable=true]').type('my {ctrl+b}bold{ctrl+b} message');
|
cy.get("div[contenteditable=true]").type("my {ctrl+b}bold{ctrl+b} message");
|
||||||
cy.get('div[aria-label="Send message"]').click();
|
cy.get('div[aria-label="Send message"]').click();
|
||||||
cy.contains('.mx_EventTile_body strong', 'bold');
|
cy.contains(".mx_EventTile_body strong", "bold");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when Ctrl+Enter is required to send", () => {
|
describe("when Ctrl+Enter is required to send", () => {
|
||||||
|
@ -140,15 +140,15 @@ describe("Composer", () => {
|
||||||
|
|
||||||
it("only sends when you press Ctrl+Enter", () => {
|
it("only sends when you press Ctrl+Enter", () => {
|
||||||
// Type a message and press Enter
|
// Type a message and press Enter
|
||||||
cy.get('div[contenteditable=true]').type('my message 3');
|
cy.get("div[contenteditable=true]").type("my message 3");
|
||||||
cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" });
|
cy.get("div[contenteditable=true]").trigger("input", { inputType: "insertParagraph" });
|
||||||
// It has not been sent yet
|
// It has not been sent yet
|
||||||
cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist');
|
cy.contains(".mx_EventTile_body", "my message 3").should("not.exist");
|
||||||
|
|
||||||
// Press Ctrl+Enter
|
// Press Ctrl+Enter
|
||||||
cy.get('div[contenteditable=true]').type('{ctrl+enter}');
|
cy.get("div[contenteditable=true]").type("{ctrl+enter}");
|
||||||
// It was sent
|
// It was sent
|
||||||
cy.contains('.mx_EventTile_body', 'my message 3');
|
cy.contains(".mx_EventTile_body", "my message 3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe("Create Room", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Jim");
|
cy.initTestUser(synapse, "Jim");
|
||||||
|
|
|
@ -28,7 +28,7 @@ interface CryptoTestContext extends Mocha.Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
const waitForVerificationRequest = (cli: MatrixClient): Promise<VerificationRequest> => {
|
const waitForVerificationRequest = (cli: MatrixClient): Promise<VerificationRequest> => {
|
||||||
return new Promise<VerificationRequest>(resolve => {
|
return new Promise<VerificationRequest>((resolve) => {
|
||||||
const onVerificationRequestEvent = (request: VerificationRequest) => {
|
const onVerificationRequestEvent = (request: VerificationRequest) => {
|
||||||
// @ts-ignore CryptoEvent is not exported to window.matrixcs; using the string value here
|
// @ts-ignore CryptoEvent is not exported to window.matrixcs; using the string value here
|
||||||
cli.off("crypto.verification.request", onVerificationRequestEvent);
|
cli.off("crypto.verification.request", onVerificationRequestEvent);
|
||||||
|
@ -59,7 +59,9 @@ const startDMWithBob = function(this: CryptoTestContext) {
|
||||||
|
|
||||||
const testMessages = function (this: CryptoTestContext) {
|
const testMessages = function (this: CryptoTestContext) {
|
||||||
// check the invite message
|
// check the invite message
|
||||||
cy.contains(".mx_EventTile_body", "Hey!").closest(".mx_EventTile").within(() => {
|
cy.contains(".mx_EventTile_body", "Hey!")
|
||||||
|
.closest(".mx_EventTile")
|
||||||
|
.within(() => {
|
||||||
cy.get(".mx_EventTile_e2eIcon_warning").should("not.exist");
|
cy.get(".mx_EventTile_e2eIcon_warning").should("not.exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -73,10 +75,11 @@ const testMessages = function(this: CryptoTestContext) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const bobJoin = function (this: CryptoTestContext) {
|
const bobJoin = function (this: CryptoTestContext) {
|
||||||
cy.window({ log: false }).then(async win => {
|
cy.window({ log: false })
|
||||||
|
.then(async (win) => {
|
||||||
const bobRooms = this.bob.getRooms();
|
const bobRooms = this.bob.getRooms();
|
||||||
if (!bobRooms.length) {
|
if (!bobRooms.length) {
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>((resolve) => {
|
||||||
const onMembership = (_event) => {
|
const onMembership = (_event) => {
|
||||||
this.bob.off(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
this.bob.off(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -84,7 +87,8 @@ const bobJoin = function(this: CryptoTestContext) {
|
||||||
this.bob.on(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
this.bob.on(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.botJoinRoomByName(this.bob, "Alice").as("bobsRoom");
|
cy.botJoinRoomByName(this.bob, "Alice").as("bobsRoom");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,7 +97,7 @@ const bobJoin = function(this: CryptoTestContext) {
|
||||||
|
|
||||||
/** configure the given MatrixClient to auto-accept any invites */
|
/** configure the given MatrixClient to auto-accept any invites */
|
||||||
function autoJoin(client: MatrixClient) {
|
function autoJoin(client: MatrixClient) {
|
||||||
cy.window({ log: false }).then(async win => {
|
cy.window({ log: false }).then(async (win) => {
|
||||||
client.on(win.matrixcs.RoomMemberEvent.Membership, (event, member) => {
|
client.on(win.matrixcs.RoomMemberEvent.Membership, (event, member) => {
|
||||||
if (member.membership === "invite" && member.userId === client.getUserId()) {
|
if (member.membership === "invite" && member.userId === client.getUserId()) {
|
||||||
client.joinRoom(member.roomId);
|
client.joinRoom(member.roomId);
|
||||||
|
@ -103,7 +107,8 @@ function autoJoin(client: MatrixClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVerificationRequest = (request: VerificationRequest): Chainable<EmojiMapping[]> => {
|
const handleVerificationRequest = (request: VerificationRequest): Chainable<EmojiMapping[]> => {
|
||||||
return cy.wrap(new Promise<EmojiMapping[]>((resolve) => {
|
return cy.wrap(
|
||||||
|
new Promise<EmojiMapping[]>((resolve) => {
|
||||||
const onShowSas = (event: ISasEvent) => {
|
const onShowSas = (event: ISasEvent) => {
|
||||||
verifier.off("show_sas", onShowSas);
|
verifier.off("show_sas", onShowSas);
|
||||||
event.confirm();
|
event.confirm();
|
||||||
|
@ -114,7 +119,8 @@ const handleVerificationRequest = (request: VerificationRequest): Chainable<Emoj
|
||||||
const verifier = request.beginKeyVerification("m.sas.v1");
|
const verifier = request.beginKeyVerification("m.sas.v1");
|
||||||
verifier.on("show_sas", onShowSas);
|
verifier.on("show_sas", onShowSas);
|
||||||
verifier.verify();
|
verifier.verify();
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const verify = function (this: CryptoTestContext) {
|
const verify = function (this: CryptoTestContext) {
|
||||||
|
@ -125,14 +131,16 @@ const verify = function(this: CryptoTestContext) {
|
||||||
cy.contains(".mx_EntityTile_name", "Bob").click();
|
cy.contains(".mx_EntityTile_name", "Bob").click();
|
||||||
cy.contains(".mx_UserInfo_verifyButton", "Verify").click();
|
cy.contains(".mx_UserInfo_verifyButton", "Verify").click();
|
||||||
cy.contains(".mx_AccessibleButton", "Start Verification").click();
|
cy.contains(".mx_AccessibleButton", "Start Verification").click();
|
||||||
cy.wrap(bobsVerificationRequestPromise).then((verificationRequest: VerificationRequest) => {
|
cy.wrap(bobsVerificationRequestPromise)
|
||||||
|
.then((verificationRequest: VerificationRequest) => {
|
||||||
verificationRequest.accept();
|
verificationRequest.accept();
|
||||||
return verificationRequest;
|
return verificationRequest;
|
||||||
}).as("bobsVerificationRequest");
|
})
|
||||||
|
.as("bobsVerificationRequest");
|
||||||
cy.contains(".mx_AccessibleButton", "Verify by emoji").click();
|
cy.contains(".mx_AccessibleButton", "Verify by emoji").click();
|
||||||
cy.get<VerificationRequest>("@bobsVerificationRequest").then((request: VerificationRequest) => {
|
cy.get<VerificationRequest>("@bobsVerificationRequest").then((request: VerificationRequest) => {
|
||||||
return handleVerificationRequest(request).then((emojis: EmojiMapping[]) => {
|
return handleVerificationRequest(request).then((emojis: EmojiMapping[]) => {
|
||||||
cy.get('.mx_VerificationShowSas_emojiSas_block').then((emojiBlocks) => {
|
cy.get(".mx_VerificationShowSas_emojiSas_block").then((emojiBlocks) => {
|
||||||
emojis.forEach((emoji: EmojiMapping, index: number) => {
|
emojis.forEach((emoji: EmojiMapping, index: number) => {
|
||||||
expect(emojiBlocks[index].textContent.toLowerCase()).to.eq(emoji[0] + emoji[1]);
|
expect(emojiBlocks[index].textContent.toLowerCase()).to.eq(emoji[0] + emoji[1]);
|
||||||
});
|
});
|
||||||
|
@ -147,7 +155,9 @@ const verify = function(this: CryptoTestContext) {
|
||||||
|
|
||||||
describe("Cryptography", function () {
|
describe("Cryptography", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
cy.startSynapse("default").as("synapse").then((synapse: SynapseInstance) => {
|
cy.startSynapse("default")
|
||||||
|
.as("synapse")
|
||||||
|
.then((synapse: SynapseInstance) => {
|
||||||
cy.initTestUser(synapse, "Alice");
|
cy.initTestUser(synapse, "Alice");
|
||||||
cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false }).as("bob");
|
cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false }).as("bob");
|
||||||
});
|
});
|
||||||
|
@ -176,10 +186,7 @@ describe("Cryptography", function() {
|
||||||
cy.bootstrapCrossSigning();
|
cy.bootstrapCrossSigning();
|
||||||
startDMWithBob.call(this);
|
startDMWithBob.call(this);
|
||||||
// send first message
|
// send first message
|
||||||
cy.get(".mx_BasicMessageComposer_input")
|
cy.get(".mx_BasicMessageComposer_input").click().should("have.focus").type("Hey!{enter}");
|
||||||
.click()
|
|
||||||
.should("have.focus")
|
|
||||||
.type("Hey!{enter}");
|
|
||||||
checkDMRoom();
|
checkDMRoom();
|
||||||
bobJoin.call(this);
|
bobJoin.call(this);
|
||||||
testMessages.call(this);
|
testMessages.call(this);
|
||||||
|
@ -192,7 +199,7 @@ describe("Cryptography", function() {
|
||||||
|
|
||||||
/* we need to have a room with the other user present, so we can open the verification panel */
|
/* we need to have a room with the other user present, so we can open the verification panel */
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({ name: "TestRoom", invite: [this.bob.getUserId()] }).then(_room1Id => {
|
cy.createRoom({ name: "TestRoom", invite: [this.bob.getUserId()] }).then((_room1Id) => {
|
||||||
roomId = _room1Id;
|
roomId = _room1Id;
|
||||||
cy.log(`Created test room ${roomId}`);
|
cy.log(`Created test room ${roomId}`);
|
||||||
cy.visit(`/#/room/${roomId}`);
|
cy.visit(`/#/room/${roomId}`);
|
||||||
|
|
|
@ -24,19 +24,14 @@ import { SynapseInstance } from "../../plugins/synapsedocker";
|
||||||
import Chainable = Cypress.Chainable;
|
import Chainable = Cypress.Chainable;
|
||||||
|
|
||||||
const sendEvent = (roomId: string): Chainable<ISendEventResponse> => {
|
const sendEvent = (roomId: string): Chainable<ISendEventResponse> => {
|
||||||
return cy.sendEvent(
|
return cy.sendEvent(roomId, null, "m.room.message" as EventType, MessageEvent.from("Message").serialize().content);
|
||||||
roomId,
|
|
||||||
null,
|
|
||||||
"m.room.message" as EventType,
|
|
||||||
MessageEvent.from("Message").serialize().content,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Editing", () => {
|
describe("Editing", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, "Edith").then(() => {
|
cy.initTestUser(synapse, "Edith").then(() => {
|
||||||
cy.injectAxe();
|
cy.injectAxe();
|
||||||
|
@ -50,7 +45,7 @@ describe("Editing", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should close the composer when clicking save after making a change and undoing it", () => {
|
it("should close the composer when clicking save after making a change and undoing it", () => {
|
||||||
cy.get<string>("@roomId").then(roomId => {
|
cy.get<string>("@roomId").then((roomId) => {
|
||||||
sendEvent(roomId);
|
sendEvent(roomId);
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
|
|
@ -77,18 +77,18 @@ describe("Integration Manager: Get OpenID Token", () => {
|
||||||
let integrationManagerUrl: string;
|
let integrationManagerUrl: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then(url => {
|
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
|
||||||
integrationManagerUrl = url;
|
integrationManagerUrl = url;
|
||||||
});
|
});
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
|
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
|
||||||
cy.window().then(win => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
|
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
|
||||||
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
|
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
|
||||||
});
|
});
|
||||||
}).then(user => {
|
}).then((user) => {
|
||||||
testUser = user;
|
testUser = user;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -107,8 +107,8 @@ describe("Integration Manager: Get OpenID Token", () => {
|
||||||
}).as("integrationManager");
|
}).as("integrationManager");
|
||||||
|
|
||||||
// Succeed when checking the token is valid
|
// Succeed when checking the token is valid
|
||||||
cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, req => {
|
cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => {
|
||||||
req.continue(res => {
|
req.continue((res) => {
|
||||||
return res.send(200, {
|
return res.send(200, {
|
||||||
user_id: testUser.userId,
|
user_id: testUser.userId,
|
||||||
});
|
});
|
||||||
|
@ -127,16 +127,14 @@ describe("Integration Manager: Get OpenID Token", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should successfully obtain an openID token", () => {
|
it("should successfully obtain an openID token", () => {
|
||||||
cy.all([
|
cy.all([cy.get<{}>("@integrationManager")]).then(() => {
|
||||||
cy.get<{}>("@integrationManager"),
|
|
||||||
]).then(() => {
|
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
|
|
||||||
openIntegrationManager();
|
openIntegrationManager();
|
||||||
sendActionFromIntegrationManager(integrationManagerUrl);
|
sendActionFromIntegrationManager(integrationManagerUrl);
|
||||||
|
|
||||||
cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
|
cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
|
||||||
cy.get("#message-response").should('include.text', 'access_token');
|
cy.get("#message-response").should("include.text", "access_token");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -87,8 +87,9 @@ function expectKickedMessage(shouldExist: boolean) {
|
||||||
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click({ multiple: true });
|
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click({ multiple: true });
|
||||||
|
|
||||||
// Check for the event message (or lack thereof)
|
// Check for the event message (or lack thereof)
|
||||||
cy.contains(".mx_EventTile_line", `${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`)
|
cy.contains(".mx_EventTile_line", `${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`).should(
|
||||||
.should(shouldExist ? "exist" : "not.exist");
|
shouldExist ? "exist" : "not.exist",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Integration Manager: Kick", () => {
|
describe("Integration Manager: Kick", () => {
|
||||||
|
@ -97,18 +98,18 @@ describe("Integration Manager: Kick", () => {
|
||||||
let integrationManagerUrl: string;
|
let integrationManagerUrl: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then(url => {
|
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
|
||||||
integrationManagerUrl = url;
|
integrationManagerUrl = url;
|
||||||
});
|
});
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
|
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
|
||||||
cy.window().then(win => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
|
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
|
||||||
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
|
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
|
||||||
});
|
});
|
||||||
}).then(user => {
|
}).then((user) => {
|
||||||
testUser = user;
|
testUser = user;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -127,8 +128,8 @@ describe("Integration Manager: Kick", () => {
|
||||||
}).as("integrationManager");
|
}).as("integrationManager");
|
||||||
|
|
||||||
// Succeed when checking the token is valid
|
// Succeed when checking the token is valid
|
||||||
cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, req => {
|
cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => {
|
||||||
req.continue(res => {
|
req.continue((res) => {
|
||||||
return res.send(200, {
|
return res.send(200, {
|
||||||
user_id: testUser.userId,
|
user_id: testUser.userId,
|
||||||
});
|
});
|
||||||
|
@ -149,96 +150,92 @@ describe("Integration Manager: Kick", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should kick the target", () => {
|
it("should kick the target", () => {
|
||||||
cy.all([
|
cy.all([cy.get<MatrixClient>("@bob"), cy.get<string>("@roomId"), cy.get<{}>("@integrationManager")]).then(
|
||||||
cy.get<MatrixClient>("@bob"),
|
([targetUser, roomId]) => {
|
||||||
cy.get<string>("@roomId"),
|
|
||||||
cy.get<{}>("@integrationManager"),
|
|
||||||
]).then(([targetUser, roomId]) => {
|
|
||||||
const targetUserId = targetUser.getUserId();
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
cy.inviteUser(roomId, targetUserId);
|
cy.inviteUser(roomId, targetUserId);
|
||||||
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist');
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
|
||||||
|
|
||||||
openIntegrationManager();
|
openIntegrationManager();
|
||||||
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
||||||
closeIntegrationManager(integrationManagerUrl);
|
closeIntegrationManager(integrationManagerUrl);
|
||||||
expectKickedMessage(true);
|
expectKickedMessage(true);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not kick the target if lacking permissions", () => {
|
it("should not kick the target if lacking permissions", () => {
|
||||||
cy.all([
|
cy.all([cy.get<MatrixClient>("@bob"), cy.get<string>("@roomId"), cy.get<{}>("@integrationManager")]).then(
|
||||||
cy.get<MatrixClient>("@bob"),
|
([targetUser, roomId]) => {
|
||||||
cy.get<string>("@roomId"),
|
|
||||||
cy.get<{}>("@integrationManager"),
|
|
||||||
]).then(([targetUser, roomId]) => {
|
|
||||||
const targetUserId = targetUser.getUserId();
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
cy.inviteUser(roomId, targetUserId);
|
cy.inviteUser(roomId, targetUserId);
|
||||||
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist');
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
|
||||||
cy.getClient().then(async client => {
|
cy.getClient()
|
||||||
await client.sendStateEvent(roomId, 'm.room.power_levels', {
|
.then(async (client) => {
|
||||||
|
await client.sendStateEvent(roomId, "m.room.power_levels", {
|
||||||
kick: 50,
|
kick: 50,
|
||||||
users: {
|
users: {
|
||||||
[testUser.userId]: 0,
|
[testUser.userId]: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
openIntegrationManager();
|
openIntegrationManager();
|
||||||
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
||||||
closeIntegrationManager(integrationManagerUrl);
|
closeIntegrationManager(integrationManagerUrl);
|
||||||
expectKickedMessage(false);
|
expectKickedMessage(false);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should no-op if the target already left", () => {
|
it("should no-op if the target already left", () => {
|
||||||
cy.all([
|
cy.all([cy.get<MatrixClient>("@bob"), cy.get<string>("@roomId"), cy.get<{}>("@integrationManager")]).then(
|
||||||
cy.get<MatrixClient>("@bob"),
|
([targetUser, roomId]) => {
|
||||||
cy.get<string>("@roomId"),
|
|
||||||
cy.get<{}>("@integrationManager"),
|
|
||||||
]).then(([targetUser, roomId]) => {
|
|
||||||
const targetUserId = targetUser.getUserId();
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
cy.inviteUser(roomId, targetUserId);
|
cy.inviteUser(roomId, targetUserId);
|
||||||
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist').then(async () => {
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`)
|
||||||
|
.should("exist")
|
||||||
|
.then(async () => {
|
||||||
await targetUser.leave(roomId);
|
await targetUser.leave(roomId);
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
openIntegrationManager();
|
openIntegrationManager();
|
||||||
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
||||||
closeIntegrationManager(integrationManagerUrl);
|
closeIntegrationManager(integrationManagerUrl);
|
||||||
expectKickedMessage(false);
|
expectKickedMessage(false);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should no-op if the target was banned", () => {
|
it("should no-op if the target was banned", () => {
|
||||||
cy.all([
|
cy.all([cy.get<MatrixClient>("@bob"), cy.get<string>("@roomId"), cy.get<{}>("@integrationManager")]).then(
|
||||||
cy.get<MatrixClient>("@bob"),
|
([targetUser, roomId]) => {
|
||||||
cy.get<string>("@roomId"),
|
|
||||||
cy.get<{}>("@integrationManager"),
|
|
||||||
]).then(([targetUser, roomId]) => {
|
|
||||||
const targetUserId = targetUser.getUserId();
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
cy.inviteUser(roomId, targetUserId);
|
cy.inviteUser(roomId, targetUserId);
|
||||||
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist');
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
|
||||||
cy.getClient().then(async client => {
|
cy.getClient()
|
||||||
|
.then(async (client) => {
|
||||||
await client.ban(roomId, targetUserId);
|
await client.ban(roomId, targetUserId);
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
openIntegrationManager();
|
openIntegrationManager();
|
||||||
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
||||||
closeIntegrationManager(integrationManagerUrl);
|
closeIntegrationManager(integrationManagerUrl);
|
||||||
expectKickedMessage(false);
|
expectKickedMessage(false);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should no-op if the target was never a room member", () => {
|
it("should no-op if the target was never a room member", () => {
|
||||||
cy.all([
|
cy.all([cy.get<MatrixClient>("@bob"), cy.get<string>("@roomId"), cy.get<{}>("@integrationManager")]).then(
|
||||||
cy.get<MatrixClient>("@bob"),
|
([targetUser, roomId]) => {
|
||||||
cy.get<string>("@roomId"),
|
|
||||||
cy.get<{}>("@integrationManager"),
|
|
||||||
]).then(([targetUser, roomId]) => {
|
|
||||||
const targetUserId = targetUser.getUserId();
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
|
|
||||||
|
@ -246,6 +243,7 @@ describe("Integration Manager: Kick", () => {
|
||||||
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
||||||
closeIntegrationManager(integrationManagerUrl);
|
closeIntegrationManager(integrationManagerUrl);
|
||||||
expectKickedMessage(false);
|
expectKickedMessage(false);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,11 +31,11 @@ describe("Lazy Loading", () => {
|
||||||
const charlies: Charly[] = [];
|
const charlies: Charly[] = [];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window().then(win => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Alice");
|
cy.initTestUser(synapse, "Alice");
|
||||||
|
@ -44,7 +44,7 @@ describe("Lazy Loading", () => {
|
||||||
displayName: "Bob",
|
displayName: "Bob",
|
||||||
startClient: false,
|
startClient: false,
|
||||||
autoAcceptInvites: false,
|
autoAcceptInvites: false,
|
||||||
}).then(_bob => {
|
}).then((_bob) => {
|
||||||
bob = _bob;
|
bob = _bob;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ describe("Lazy Loading", () => {
|
||||||
displayName,
|
displayName,
|
||||||
startClient: false,
|
startClient: false,
|
||||||
autoAcceptInvites: false,
|
autoAcceptInvites: false,
|
||||||
}).then(client => {
|
}).then((client) => {
|
||||||
charlies[i - 1] = { displayName, client };
|
charlies[i - 1] = { displayName, client };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -71,15 +71,22 @@ describe("Lazy Loading", () => {
|
||||||
const charlyMsg2 = "how's it going??";
|
const charlyMsg2 = "how's it going??";
|
||||||
|
|
||||||
function setupRoomWithBobAliceAndCharlies(charlies: Charly[]) {
|
function setupRoomWithBobAliceAndCharlies(charlies: Charly[]) {
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
return cy.wrap(bob.createRoom({
|
return cy
|
||||||
|
.wrap(
|
||||||
|
bob
|
||||||
|
.createRoom({
|
||||||
name,
|
name,
|
||||||
room_alias_name: "lltest",
|
room_alias_name: "lltest",
|
||||||
visibility: win.matrixcs.Visibility.Public,
|
visibility: win.matrixcs.Visibility.Public,
|
||||||
}).then(r => r.room_id), { log: false }).as("roomId");
|
})
|
||||||
|
.then((r) => r.room_id),
|
||||||
|
{ log: false },
|
||||||
|
)
|
||||||
|
.as("roomId");
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get<string>("@roomId").then(async roomId => {
|
cy.get<string>("@roomId").then(async (roomId) => {
|
||||||
for (const charly of charlies) {
|
for (const charly of charlies) {
|
||||||
await charly.client.joinRoom(alias);
|
await charly.client.joinRoom(alias);
|
||||||
}
|
}
|
||||||
|
@ -122,13 +129,13 @@ describe("Lazy Loading", () => {
|
||||||
function checkMemberList(charlies: Charly[]) {
|
function checkMemberList(charlies: Charly[]) {
|
||||||
getMemberInMemberlist("Alice").should("exist");
|
getMemberInMemberlist("Alice").should("exist");
|
||||||
getMemberInMemberlist("Bob").should("exist");
|
getMemberInMemberlist("Bob").should("exist");
|
||||||
charlies.forEach(charly => {
|
charlies.forEach((charly) => {
|
||||||
getMemberInMemberlist(charly.displayName).should("exist");
|
getMemberInMemberlist(charly.displayName).should("exist");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkMemberListLacksCharlies(charlies: Charly[]) {
|
function checkMemberListLacksCharlies(charlies: Charly[]) {
|
||||||
charlies.forEach(charly => {
|
charlies.forEach((charly) => {
|
||||||
getMemberInMemberlist(charly.displayName).should("not.exist");
|
getMemberInMemberlist(charly.displayName).should("not.exist");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -136,7 +143,7 @@ describe("Lazy Loading", () => {
|
||||||
function joinCharliesWhileAliceIsOffline(charlies: Charly[]) {
|
function joinCharliesWhileAliceIsOffline(charlies: Charly[]) {
|
||||||
cy.goOffline();
|
cy.goOffline();
|
||||||
|
|
||||||
cy.get<string>("@roomId").then(async roomId => {
|
cy.get<string>("@roomId").then(async (roomId) => {
|
||||||
for (const charly of charlies) {
|
for (const charly of charlies) {
|
||||||
await charly.client.joinRoom(alias);
|
await charly.client.joinRoom(alias);
|
||||||
}
|
}
|
||||||
|
@ -163,7 +170,7 @@ describe("Lazy Loading", () => {
|
||||||
joinCharliesWhileAliceIsOffline(charly6to10);
|
joinCharliesWhileAliceIsOffline(charly6to10);
|
||||||
checkMemberList(charly6to10);
|
checkMemberList(charly6to10);
|
||||||
|
|
||||||
cy.get<string>("@roomId").then(async roomId => {
|
cy.get<string>("@roomId").then(async (roomId) => {
|
||||||
for (const charly of charlies) {
|
for (const charly of charlies) {
|
||||||
await charly.client.leave(roomId);
|
await charly.client.leave(roomId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,10 @@ describe("Location sharing", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window().then(win => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
||||||
});
|
});
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Tom");
|
cy.initTestUser(synapse, "Tom");
|
||||||
|
@ -47,31 +47,28 @@ describe("Location sharing", () => {
|
||||||
|
|
||||||
it("sends and displays pin drop location message successfully", () => {
|
it("sends and displays pin drop location message successfully", () => {
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.visit('/#/room/' + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.openMessageComposerOptions().within(() => {
|
cy.openMessageComposerOptions().within(() => {
|
||||||
cy.get('[aria-label="Location"]').click();
|
cy.get('[aria-label="Location"]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
selectLocationShareTypeOption('Pin').click();
|
selectLocationShareTypeOption("Pin").click();
|
||||||
|
|
||||||
cy.get('#mx_LocationPicker_map').click('center');
|
cy.get("#mx_LocationPicker_map").click("center");
|
||||||
|
|
||||||
submitShareLocation();
|
submitShareLocation();
|
||||||
|
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile .mx_MLocationBody", { timeout: 10000 })
|
cy.get(".mx_RoomView_body .mx_EventTile .mx_MLocationBody", { timeout: 10000 }).should("exist").click();
|
||||||
.should('exist')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// clicking location tile opens maximised map
|
// clicking location tile opens maximised map
|
||||||
cy.get('.mx_LocationViewDialog_wrapper').should('exist');
|
cy.get(".mx_LocationViewDialog_wrapper").should("exist");
|
||||||
|
|
||||||
cy.get('[aria-label="Close dialog"]').click();
|
cy.get('[aria-label="Close dialog"]').click();
|
||||||
|
|
||||||
cy.get('.mx_Marker')
|
cy.get(".mx_Marker").should("exist");
|
||||||
.should('exist');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,7 +24,7 @@ describe("Consent", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("consent").then(data => {
|
cy.startSynapse("consent").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Bob");
|
cy.initTestUser(synapse, "Bob");
|
||||||
|
@ -37,7 +37,7 @@ describe("Consent", () => {
|
||||||
|
|
||||||
it("should prompt the user to consent to terms when server deems it necessary", () => {
|
it("should prompt the user to consent to terms when server deems it necessary", () => {
|
||||||
// Attempt to create a room using the js-sdk which should return an error with `M_CONSENT_NOT_GIVEN`
|
// Attempt to create a room using the js-sdk which should return an error with `M_CONSENT_NOT_GIVEN`
|
||||||
cy.window().then(win => {
|
cy.window().then((win) => {
|
||||||
win.mxMatrixClientPeg.matrixClient.createRoom({}).catch(() => {});
|
win.mxMatrixClientPeg.matrixClient.createRoom({}).catch(() => {});
|
||||||
|
|
||||||
// Stub `window.open` - clicking the primary button below will call it
|
// Stub `window.open` - clicking the primary button below will call it
|
||||||
|
@ -50,7 +50,7 @@ describe("Consent", () => {
|
||||||
cy.get(".mx_Dialog_primary").click();
|
cy.get(".mx_Dialog_primary").click();
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get<SinonStub>("@windowOpen").then(stub => {
|
cy.get<SinonStub>("@windowOpen").then((stub) => {
|
||||||
const url = stub.getCall(0).args[0];
|
const url = stub.getCall(0).args[0];
|
||||||
|
|
||||||
// Go to Synapse's consent page and accept it
|
// Go to Synapse's consent page and accept it
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe("Login", () => {
|
||||||
const password = "p4s5W0rD";
|
const password = "p4s5W0rD";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("consent").then(data => {
|
cy.startSynapse("consent").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.registerUser(synapse, username, password);
|
cy.registerUser(synapse, username, password);
|
||||||
cy.visit("/#/login");
|
cy.visit("/#/login");
|
||||||
|
@ -52,19 +52,19 @@ describe("Login", () => {
|
||||||
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
||||||
cy.get(".mx_ServerPickerDialog_continue").click();
|
cy.get(".mx_ServerPickerDialog_continue").click();
|
||||||
// wait for the dialog to go away
|
// wait for the dialog to go away
|
||||||
cy.get('.mx_ServerPickerDialog').should('not.exist');
|
cy.get(".mx_ServerPickerDialog").should("not.exist");
|
||||||
|
|
||||||
cy.get("#mx_LoginForm_username").type(username);
|
cy.get("#mx_LoginForm_username").type(username);
|
||||||
cy.get("#mx_LoginForm_password").type(password);
|
cy.get("#mx_LoginForm_password").type(password);
|
||||||
cy.get(".mx_Login_submit").click();
|
cy.get(".mx_Login_submit").click();
|
||||||
|
|
||||||
cy.url().should('contain', '/#/home', { timeout: 30000 });
|
cy.url().should("contain", "/#/home", { timeout: 30000 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("logout", () => {
|
describe("logout", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("consent").then(data => {
|
cy.startSynapse("consent").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, "Erin");
|
cy.initTestUser(synapse, "Erin");
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,17 +33,17 @@ describe("Polls", () => {
|
||||||
};
|
};
|
||||||
const createPoll = ({ title, options }: CreatePollOptions) => {
|
const createPoll = ({ title, options }: CreatePollOptions) => {
|
||||||
if (options.length < 2) {
|
if (options.length < 2) {
|
||||||
throw new Error('Poll must have at least two options');
|
throw new Error("Poll must have at least two options");
|
||||||
}
|
}
|
||||||
cy.get('.mx_PollCreateDialog').within((pollCreateDialog) => {
|
cy.get(".mx_PollCreateDialog").within((pollCreateDialog) => {
|
||||||
cy.get('#poll-topic-input').type(title);
|
cy.get("#poll-topic-input").type(title);
|
||||||
|
|
||||||
options.forEach((option, index) => {
|
options.forEach((option, index) => {
|
||||||
const optionId = `#pollcreate_option_${index}`;
|
const optionId = `#pollcreate_option_${index}`;
|
||||||
|
|
||||||
// click 'add option' button if needed
|
// click 'add option' button if needed
|
||||||
if (pollCreateDialog.find(optionId).length === 0) {
|
if (pollCreateDialog.find(optionId).length === 0) {
|
||||||
cy.get('.mx_PollCreateDialog_addOption').scrollIntoView().click();
|
cy.get(".mx_PollCreateDialog_addOption").scrollIntoView().click();
|
||||||
}
|
}
|
||||||
cy.get(optionId).scrollIntoView().type(option);
|
cy.get(optionId).scrollIntoView().type(option);
|
||||||
});
|
});
|
||||||
|
@ -56,34 +56,32 @@ describe("Polls", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPollOption = (pollId: string, optionText: string): Chainable<JQuery> => {
|
const getPollOption = (pollId: string, optionText: string): Chainable<JQuery> => {
|
||||||
return getPollTile(pollId).contains('.mx_MPollBody_option .mx_StyledRadioButton', optionText);
|
return getPollTile(pollId).contains(".mx_MPollBody_option .mx_StyledRadioButton", optionText);
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => {
|
const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => {
|
||||||
getPollOption(pollId, optionText).within(() => {
|
getPollOption(pollId, optionText).within(() => {
|
||||||
cy.get('.mx_MPollBody_optionVoteCount').should('contain', `${votes} vote`);
|
cy.get(".mx_MPollBody_optionVoteCount").should("contain", `${votes} vote`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const botVoteForOption = (bot: MatrixClient, roomId: string, pollId: string, optionText: string): void => {
|
const botVoteForOption = (bot: MatrixClient, roomId: string, pollId: string, optionText: string): void => {
|
||||||
getPollOption(pollId, optionText).within(ref => {
|
getPollOption(pollId, optionText).within((ref) => {
|
||||||
cy.get('input[type="radio"]').invoke('attr', 'value').then(optionId => {
|
cy.get('input[type="radio"]')
|
||||||
|
.invoke("attr", "value")
|
||||||
|
.then((optionId) => {
|
||||||
const pollVote = PollResponseEvent.from([optionId], pollId).serialize();
|
const pollVote = PollResponseEvent.from([optionId], pollId).serialize();
|
||||||
bot.sendEvent(
|
bot.sendEvent(roomId, pollVote.type, pollVote.content);
|
||||||
roomId,
|
|
||||||
pollVote.type,
|
|
||||||
pollVote.content,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.enableLabsFeature("feature_thread");
|
cy.enableLabsFeature("feature_thread");
|
||||||
cy.window().then(win => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
||||||
});
|
});
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Tom");
|
cy.initTestUser(synapse, "Tom");
|
||||||
|
@ -96,15 +94,15 @@ describe("Polls", () => {
|
||||||
|
|
||||||
it("should be creatable and votable", () => {
|
it("should be creatable and votable", () => {
|
||||||
let bot: MatrixClient;
|
let bot: MatrixClient;
|
||||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
|
||||||
bot = _bot;
|
bot = _bot;
|
||||||
});
|
});
|
||||||
|
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.inviteUser(roomId, bot.getUserId());
|
cy.inviteUser(roomId, bot.getUserId());
|
||||||
cy.visit('/#/room/' + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
// wait until Bob joined
|
// wait until Bob joined
|
||||||
cy.contains(".mx_TextualEvent", "BotBob joined the room").should("exist");
|
cy.contains(".mx_TextualEvent", "BotBob joined the room").should("exist");
|
||||||
});
|
});
|
||||||
|
@ -113,34 +111,35 @@ describe("Polls", () => {
|
||||||
cy.get('[aria-label="Poll"]').click();
|
cy.get('[aria-label="Poll"]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('.mx_CompoundDialog').percySnapshotElement('Polls Composer');
|
cy.get(".mx_CompoundDialog").percySnapshotElement("Polls Composer");
|
||||||
|
|
||||||
const pollParams = {
|
const pollParams = {
|
||||||
title: 'Does the polls feature work?',
|
title: "Does the polls feature work?",
|
||||||
options: ['Yes', 'No', 'Maybe'],
|
options: ["Yes", "No", "Maybe"],
|
||||||
};
|
};
|
||||||
createPoll(pollParams);
|
createPoll(pollParams);
|
||||||
|
|
||||||
// Wait for message to send, get its ID and save as @pollId
|
// Wait for message to send, get its ID and save as @pollId
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title)
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
.invoke("attr", "data-scroll-tokens")
|
||||||
|
.as("pollId");
|
||||||
|
|
||||||
cy.get<string>("@pollId").then(pollId => {
|
cy.get<string>("@pollId").then((pollId) => {
|
||||||
getPollTile(pollId).percySnapshotElement('Polls Timeline tile - no votes', { percyCSS: hideTimestampCSS });
|
getPollTile(pollId).percySnapshotElement("Polls Timeline tile - no votes", { percyCSS: hideTimestampCSS });
|
||||||
|
|
||||||
// Bot votes 'Maybe' in the poll
|
// Bot votes 'Maybe' in the poll
|
||||||
botVoteForOption(bot, roomId, pollId, pollParams.options[2]);
|
botVoteForOption(bot, roomId, pollId, pollParams.options[2]);
|
||||||
|
|
||||||
// no votes shown until I vote, check bots vote has arrived
|
// no votes shown until I vote, check bots vote has arrived
|
||||||
cy.get('.mx_MPollBody_totalVotes').should('contain', '1 vote cast');
|
cy.get(".mx_MPollBody_totalVotes").should("contain", "1 vote cast");
|
||||||
|
|
||||||
// vote 'Maybe'
|
// vote 'Maybe'
|
||||||
getPollOption(pollId, pollParams.options[2]).click('topLeft');
|
getPollOption(pollId, pollParams.options[2]).click("topLeft");
|
||||||
// both me and bot have voted Maybe
|
// both me and bot have voted Maybe
|
||||||
expectPollOptionVoteCount(pollId, pollParams.options[2], 2);
|
expectPollOptionVoteCount(pollId, pollParams.options[2], 2);
|
||||||
|
|
||||||
// change my vote to 'Yes'
|
// change my vote to 'Yes'
|
||||||
getPollOption(pollId, pollParams.options[0]).click('topLeft');
|
getPollOption(pollId, pollParams.options[0]).click("topLeft");
|
||||||
|
|
||||||
// 1 vote for yes
|
// 1 vote for yes
|
||||||
expectPollOptionVoteCount(pollId, pollParams.options[0], 1);
|
expectPollOptionVoteCount(pollId, pollParams.options[0], 1);
|
||||||
|
@ -161,15 +160,15 @@ describe("Polls", () => {
|
||||||
|
|
||||||
it("should be editable from context menu if no votes have been cast", () => {
|
it("should be editable from context menu if no votes have been cast", () => {
|
||||||
let bot: MatrixClient;
|
let bot: MatrixClient;
|
||||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
|
||||||
bot = _bot;
|
bot = _bot;
|
||||||
});
|
});
|
||||||
|
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.inviteUser(roomId, bot.getUserId());
|
cy.inviteUser(roomId, bot.getUserId());
|
||||||
cy.visit('/#/room/' + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.openMessageComposerOptions().within(() => {
|
cy.openMessageComposerOptions().within(() => {
|
||||||
|
@ -177,40 +176,42 @@ describe("Polls", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const pollParams = {
|
const pollParams = {
|
||||||
title: 'Does the polls feature work?',
|
title: "Does the polls feature work?",
|
||||||
options: ['Yes', 'No', 'Maybe'],
|
options: ["Yes", "No", "Maybe"],
|
||||||
};
|
};
|
||||||
createPoll(pollParams);
|
createPoll(pollParams);
|
||||||
|
|
||||||
// Wait for message to send, get its ID and save as @pollId
|
// Wait for message to send, get its ID and save as @pollId
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
cy.get(".mx_RoomView_body .mx_EventTile")
|
||||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
.contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||||
|
.invoke("attr", "data-scroll-tokens")
|
||||||
|
.as("pollId");
|
||||||
|
|
||||||
cy.get<string>("@pollId").then(pollId => {
|
cy.get<string>("@pollId").then((pollId) => {
|
||||||
// Open context menu
|
// Open context menu
|
||||||
getPollTile(pollId).rightclick();
|
getPollTile(pollId).rightclick();
|
||||||
|
|
||||||
// Select edit item
|
// Select edit item
|
||||||
cy.get('.mx_ContextualMenu').within(() => {
|
cy.get(".mx_ContextualMenu").within(() => {
|
||||||
cy.get('[aria-label="Edit"]').click();
|
cy.get('[aria-label="Edit"]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Expect poll editing dialog
|
// Expect poll editing dialog
|
||||||
cy.get('.mx_PollCreateDialog');
|
cy.get(".mx_PollCreateDialog");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be editable from context menu if votes have been cast", () => {
|
it("should not be editable from context menu if votes have been cast", () => {
|
||||||
let bot: MatrixClient;
|
let bot: MatrixClient;
|
||||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
|
||||||
bot = _bot;
|
bot = _bot;
|
||||||
});
|
});
|
||||||
|
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.inviteUser(roomId, bot.getUserId());
|
cy.inviteUser(roomId, bot.getUserId());
|
||||||
cy.visit('/#/room/' + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.openMessageComposerOptions().within(() => {
|
cy.openMessageComposerOptions().within(() => {
|
||||||
|
@ -218,51 +219,53 @@ describe("Polls", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const pollParams = {
|
const pollParams = {
|
||||||
title: 'Does the polls feature work?',
|
title: "Does the polls feature work?",
|
||||||
options: ['Yes', 'No', 'Maybe'],
|
options: ["Yes", "No", "Maybe"],
|
||||||
};
|
};
|
||||||
createPoll(pollParams);
|
createPoll(pollParams);
|
||||||
|
|
||||||
// Wait for message to send, get its ID and save as @pollId
|
// Wait for message to send, get its ID and save as @pollId
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
cy.get(".mx_RoomView_body .mx_EventTile")
|
||||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
.contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||||
|
.invoke("attr", "data-scroll-tokens")
|
||||||
|
.as("pollId");
|
||||||
|
|
||||||
cy.get<string>("@pollId").then(pollId => {
|
cy.get<string>("@pollId").then((pollId) => {
|
||||||
// Bot votes 'Maybe' in the poll
|
// Bot votes 'Maybe' in the poll
|
||||||
botVoteForOption(bot, roomId, pollId, pollParams.options[2]);
|
botVoteForOption(bot, roomId, pollId, pollParams.options[2]);
|
||||||
|
|
||||||
// wait for bot's vote to arrive
|
// wait for bot's vote to arrive
|
||||||
cy.get('.mx_MPollBody_totalVotes').should('contain', '1 vote cast');
|
cy.get(".mx_MPollBody_totalVotes").should("contain", "1 vote cast");
|
||||||
|
|
||||||
// Open context menu
|
// Open context menu
|
||||||
getPollTile(pollId).rightclick();
|
getPollTile(pollId).rightclick();
|
||||||
|
|
||||||
// Select edit item
|
// Select edit item
|
||||||
cy.get('.mx_ContextualMenu').within(() => {
|
cy.get(".mx_ContextualMenu").within(() => {
|
||||||
cy.get('[aria-label="Edit"]').click();
|
cy.get('[aria-label="Edit"]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Expect error dialog
|
// Expect error dialog
|
||||||
cy.get('.mx_ErrorDialog');
|
cy.get(".mx_ErrorDialog");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be displayed correctly in thread panel", () => {
|
it("should be displayed correctly in thread panel", () => {
|
||||||
let botBob: MatrixClient;
|
let botBob: MatrixClient;
|
||||||
let botCharlie: MatrixClient;
|
let botCharlie: MatrixClient;
|
||||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
|
||||||
botBob = _bot;
|
botBob = _bot;
|
||||||
});
|
});
|
||||||
cy.getBot(synapse, { displayName: "BotCharlie" }).then(_bot => {
|
cy.getBot(synapse, { displayName: "BotCharlie" }).then((_bot) => {
|
||||||
botCharlie = _bot;
|
botCharlie = _bot;
|
||||||
});
|
});
|
||||||
|
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.inviteUser(roomId, botBob.getUserId());
|
cy.inviteUser(roomId, botBob.getUserId());
|
||||||
cy.inviteUser(roomId, botCharlie.getUserId());
|
cy.inviteUser(roomId, botCharlie.getUserId());
|
||||||
cy.visit('/#/room/' + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
// wait until the bots joined
|
// wait until the bots joined
|
||||||
cy.contains(".mx_TextualEvent", "and one other were invited and joined").should("exist");
|
cy.contains(".mx_TextualEvent", "and one other were invited and joined").should("exist");
|
||||||
});
|
});
|
||||||
|
@ -272,16 +275,17 @@ describe("Polls", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const pollParams = {
|
const pollParams = {
|
||||||
title: 'Does the polls feature work?',
|
title: "Does the polls feature work?",
|
||||||
options: ['Yes', 'No', 'Maybe'],
|
options: ["Yes", "No", "Maybe"],
|
||||||
};
|
};
|
||||||
createPoll(pollParams);
|
createPoll(pollParams);
|
||||||
|
|
||||||
// Wait for message to send, get its ID and save as @pollId
|
// Wait for message to send, get its ID and save as @pollId
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title)
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
.invoke("attr", "data-scroll-tokens")
|
||||||
|
.as("pollId");
|
||||||
|
|
||||||
cy.get<string>("@pollId").then(pollId => {
|
cy.get<string>("@pollId").then((pollId) => {
|
||||||
// Bob starts thread on the poll
|
// Bob starts thread on the poll
|
||||||
botBob.sendMessage(roomId, pollId, {
|
botBob.sendMessage(roomId, pollId, {
|
||||||
body: "Hello there",
|
body: "Hello there",
|
||||||
|
@ -297,22 +301,22 @@ describe("Polls", () => {
|
||||||
botVoteForOption(botCharlie, roomId, pollId, pollParams.options[1]);
|
botVoteForOption(botCharlie, roomId, pollId, pollParams.options[1]);
|
||||||
|
|
||||||
// no votes shown until I vote, check votes have arrived in main tl
|
// no votes shown until I vote, check votes have arrived in main tl
|
||||||
cy.get('.mx_RoomView_body .mx_MPollBody_totalVotes').should('contain', '2 votes cast');
|
cy.get(".mx_RoomView_body .mx_MPollBody_totalVotes").should("contain", "2 votes cast");
|
||||||
// and thread view
|
// and thread view
|
||||||
cy.get('.mx_ThreadView .mx_MPollBody_totalVotes').should('contain', '2 votes cast');
|
cy.get(".mx_ThreadView .mx_MPollBody_totalVotes").should("contain", "2 votes cast");
|
||||||
|
|
||||||
cy.get('.mx_RoomView_body').within(() => {
|
cy.get(".mx_RoomView_body").within(() => {
|
||||||
// vote 'Maybe' in the main timeline poll
|
// vote 'Maybe' in the main timeline poll
|
||||||
getPollOption(pollId, pollParams.options[2]).click('topLeft');
|
getPollOption(pollId, pollParams.options[2]).click("topLeft");
|
||||||
// both me and bob have voted Maybe
|
// both me and bob have voted Maybe
|
||||||
expectPollOptionVoteCount(pollId, pollParams.options[2], 2);
|
expectPollOptionVoteCount(pollId, pollParams.options[2], 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('.mx_ThreadView').within(() => {
|
cy.get(".mx_ThreadView").within(() => {
|
||||||
// votes updated in thread view too
|
// votes updated in thread view too
|
||||||
expectPollOptionVoteCount(pollId, pollParams.options[2], 2);
|
expectPollOptionVoteCount(pollId, pollParams.options[2], 2);
|
||||||
// change my vote to 'Yes'
|
// change my vote to 'Yes'
|
||||||
getPollOption(pollId, pollParams.options[0]).click('topLeft');
|
getPollOption(pollId, pollParams.options[0]).click("topLeft");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bob updates vote to 'No'
|
// Bob updates vote to 'No'
|
||||||
|
@ -329,11 +333,11 @@ describe("Polls", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// check counts are correct in main timeline tile
|
// check counts are correct in main timeline tile
|
||||||
cy.get('.mx_RoomView_body').within(() => {
|
cy.get(".mx_RoomView_body").within(() => {
|
||||||
expectVoteCounts();
|
expectVoteCounts();
|
||||||
});
|
});
|
||||||
// and in thread view tile
|
// and in thread view tile
|
||||||
cy.get('.mx_ThreadView').within(() => {
|
cy.get(".mx_ThreadView").within(() => {
|
||||||
expectVoteCounts();
|
expectVoteCounts();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,7 +24,7 @@ describe("Registration", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.stubDefaultServer();
|
cy.stubDefaultServer();
|
||||||
cy.visit("/#/register");
|
cy.visit("/#/register");
|
||||||
cy.startSynapse("consent").then(data => {
|
cy.startSynapse("consent").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -45,7 +45,7 @@ describe("Registration", () => {
|
||||||
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
||||||
cy.get(".mx_ServerPickerDialog_continue").click();
|
cy.get(".mx_ServerPickerDialog_continue").click();
|
||||||
// wait for the dialog to go away
|
// wait for the dialog to go away
|
||||||
cy.get('.mx_ServerPickerDialog').should('not.exist');
|
cy.get(".mx_ServerPickerDialog").should("not.exist");
|
||||||
|
|
||||||
cy.get("#mx_RegistrationForm_username").should("be.visible");
|
cy.get("#mx_RegistrationForm_username").should("be.visible");
|
||||||
// Hide the server text as it contains the randomly allocated Synapse port
|
// Hide the server text as it contains the randomly allocated Synapse port
|
||||||
|
@ -75,12 +75,14 @@ describe("Registration", () => {
|
||||||
cy.checkA11y();
|
cy.checkA11y();
|
||||||
cy.get(".mx_UseCaseSelection_skip .mx_AccessibleButton").click();
|
cy.get(".mx_UseCaseSelection_skip .mx_AccessibleButton").click();
|
||||||
|
|
||||||
cy.url().should('contain', '/#/home');
|
cy.url().should("contain", "/#/home");
|
||||||
|
|
||||||
cy.get('[aria-label="User menu"]').click();
|
cy.get('[aria-label="User menu"]').click();
|
||||||
cy.get('[aria-label="Security & Privacy"]').click();
|
cy.get('[aria-label="Security & Privacy"]').click();
|
||||||
cy.get(".mx_DevicesPanel_myDevice .mx_DevicesPanel_deviceTrust .mx_E2EIcon")
|
cy.get(".mx_DevicesPanel_myDevice .mx_DevicesPanel_deviceTrust .mx_E2EIcon").should(
|
||||||
.should("have.class", "mx_E2EIcon_verified");
|
"have.class",
|
||||||
|
"mx_E2EIcon_verified",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should require username to fulfil requirements and be available", () => {
|
it("should require username to fulfil requirements and be available", () => {
|
||||||
|
@ -89,7 +91,7 @@ describe("Registration", () => {
|
||||||
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
||||||
cy.get(".mx_ServerPickerDialog_continue").click();
|
cy.get(".mx_ServerPickerDialog_continue").click();
|
||||||
// wait for the dialog to go away
|
// wait for the dialog to go away
|
||||||
cy.get('.mx_ServerPickerDialog').should('not.exist');
|
cy.get(".mx_ServerPickerDialog").should("not.exist");
|
||||||
|
|
||||||
cy.get("#mx_RegistrationForm_username").should("be.visible");
|
cy.get("#mx_RegistrationForm_username").should("be.visible");
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe("Pills", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Sally");
|
cy.initTestUser(synapse, "Sally");
|
||||||
|
@ -33,7 +33,7 @@ describe("Pills", () => {
|
||||||
cy.stopSynapse(synapse);
|
cy.stopSynapse(synapse);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate clicks internally to the app', () => {
|
it("should navigate clicks internally to the app", () => {
|
||||||
const messageRoom = "Send Messages Here";
|
const messageRoom = "Send Messages Here";
|
||||||
const targetLocalpart = "aliasssssssssssss";
|
const targetLocalpart = "aliasssssssssssss";
|
||||||
cy.createRoom({
|
cy.createRoom({
|
||||||
|
@ -43,16 +43,16 @@ describe("Pills", () => {
|
||||||
cy.createRoom({
|
cy.createRoom({
|
||||||
name: messageRoom,
|
name: messageRoom,
|
||||||
}).as("messageRoomId");
|
}).as("messageRoomId");
|
||||||
cy.all([
|
cy.all([cy.get<string>("@targetRoomId"), cy.get<string>("@messageRoomId")]).then(
|
||||||
cy.get<string>("@targetRoomId"),
|
([targetRoomId, messageRoomId]) => {
|
||||||
cy.get<string>("@messageRoomId"),
|
// discard the target room ID - we don't need it
|
||||||
]).then(([targetRoomId, messageRoomId]) => { // discard the target room ID - we don't need it
|
|
||||||
cy.viewRoomByName(messageRoom);
|
cy.viewRoomByName(messageRoom);
|
||||||
cy.url().should("contain", `/#/room/${messageRoomId}`);
|
cy.url().should("contain", `/#/room/${messageRoomId}`);
|
||||||
|
|
||||||
// send a message using the built-in room mention functionality (autocomplete)
|
// send a message using the built-in room mention functionality (autocomplete)
|
||||||
cy.get(".mx_SendMessageComposer .mx_BasicMessageComposer_input")
|
cy.get(".mx_SendMessageComposer .mx_BasicMessageComposer_input").type(
|
||||||
.type(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`);
|
`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`,
|
||||||
|
);
|
||||||
cy.get(".mx_Autocomplete_Completion_title").click();
|
cy.get(".mx_Autocomplete_Completion_title").click();
|
||||||
cy.get(".mx_MessageComposer_sendMessage").click();
|
cy.get(".mx_MessageComposer_sendMessage").click();
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@ describe("Pills", () => {
|
||||||
.should("have.css", "pointer-events", "none")
|
.should("have.css", "pointer-events", "none")
|
||||||
.click({ force: true }); // force is to ensure we bypass pointer-events
|
.click({ force: true }); // force is to ensure we bypass pointer-events
|
||||||
cy.url().should("contain", localUrl);
|
cy.url().should("contain", localUrl);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,7 +46,7 @@ describe("RightPanel", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, NAME).then(() =>
|
cy.initTestUser(synapse, NAME).then(() =>
|
||||||
cy.window({ log: false }).then(() => {
|
cy.window({ log: false }).then(() => {
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe("Room Directory", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Ray");
|
cy.initTestUser(synapse, "Ray");
|
||||||
|
@ -36,7 +36,7 @@ describe("Room Directory", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow admin to add alias & publish room to directory", () => {
|
it("should allow admin to add alias & publish room to directory", () => {
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
cy.createRoom({
|
cy.createRoom({
|
||||||
name: "Gaming",
|
name: "Gaming",
|
||||||
preset: win.matrixcs.Preset.PublicChat,
|
preset: win.matrixcs.Preset.PublicChat,
|
||||||
|
@ -56,16 +56,14 @@ describe("Room Directory", () => {
|
||||||
// Publish into the public rooms directory
|
// Publish into the public rooms directory
|
||||||
cy.contains(".mx_SettingsFieldset", "Published Addresses").within(() => {
|
cy.contains(".mx_SettingsFieldset", "Published Addresses").within(() => {
|
||||||
cy.get("#canonicalAlias").find(":selected").should("contain", "#gaming:localhost");
|
cy.get("#canonicalAlias").find(":selected").should("contain", "#gaming:localhost");
|
||||||
cy.get(`[aria-label="Publish this room to the public in localhost's room directory?"]`).click()
|
cy.get(`[aria-label="Publish this room to the public in localhost's room directory?"]`)
|
||||||
|
.click()
|
||||||
.should("have.attr", "aria-checked", "true");
|
.should("have.attr", "aria-checked", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.closeDialog();
|
cy.closeDialog();
|
||||||
|
|
||||||
cy.all([
|
cy.all([cy.get<MatrixClient>("@bot"), cy.get<string>("@roomId")]).then(async ([bot, roomId]) => {
|
||||||
cy.get<MatrixClient>("@bot"),
|
|
||||||
cy.get<string>("@roomId"),
|
|
||||||
]).then(async ([bot, roomId]) => {
|
|
||||||
const resp = await bot.publicRooms({});
|
const resp = await bot.publicRooms({});
|
||||||
expect(resp.total_room_count_estimate).to.equal(1);
|
expect(resp.total_room_count_estimate).to.equal(1);
|
||||||
expect(resp.chunk).to.have.length(1);
|
expect(resp.chunk).to.have.length(1);
|
||||||
|
@ -75,10 +73,7 @@ describe("Room Directory", () => {
|
||||||
|
|
||||||
it("should allow finding published rooms in directory", () => {
|
it("should allow finding published rooms in directory", () => {
|
||||||
const name = "This is a public room";
|
const name = "This is a public room";
|
||||||
cy.all([
|
cy.all([cy.window({ log: false }), cy.get<MatrixClient>("@bot")]).then(([win, bot]) => {
|
||||||
cy.window({ log: false }),
|
|
||||||
cy.get<MatrixClient>("@bot"),
|
|
||||||
]).then(([win, bot]) => {
|
|
||||||
bot.createRoom({
|
bot.createRoom({
|
||||||
visibility: win.matrixcs.Visibility.Public,
|
visibility: win.matrixcs.Visibility.Public,
|
||||||
name,
|
name,
|
||||||
|
@ -89,16 +84,17 @@ describe("Room Directory", () => {
|
||||||
cy.get('[role="button"][aria-label="Explore rooms"]').click();
|
cy.get('[role="button"][aria-label="Explore rooms"]').click();
|
||||||
|
|
||||||
cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("Unknown Room");
|
cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("Unknown Room");
|
||||||
cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_otherSearches_messageSearchText")
|
cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_otherSearches_messageSearchText").should(
|
||||||
.should("contain", "can't find the room you're looking for");
|
"contain",
|
||||||
|
"can't find the room you're looking for",
|
||||||
|
);
|
||||||
cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered no results");
|
cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered no results");
|
||||||
|
|
||||||
cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("{selectAll}{backspace}test1234");
|
cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("{selectAll}{backspace}test1234");
|
||||||
cy.contains(".mx_SpotlightDialog .mx_SpotlightDialog_result_publicRoomName", name)
|
cy.contains(".mx_SpotlightDialog .mx_SpotlightDialog_result_publicRoomName", name).should("exist");
|
||||||
.should("exist");
|
|
||||||
cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered one result");
|
cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered one result");
|
||||||
cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_option").find(".mx_AccessibleButton").contains("Join").click();
|
cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_option").find(".mx_AccessibleButton").contains("Join").click();
|
||||||
|
|
||||||
cy.url().should('contain', `/#/room/#test1234:localhost`);
|
cy.url().should("contain", `/#/room/#test1234:localhost`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,15 +25,18 @@ describe("Device manager", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.enableLabsFeature("feature_new_device_manager");
|
cy.enableLabsFeature("feature_new_device_manager");
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Alice").then(credentials => {
|
cy.initTestUser(synapse, "Alice")
|
||||||
|
.then((credentials) => {
|
||||||
user = credentials;
|
user = credentials;
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
// create some extra sessions to manage
|
// create some extra sessions to manage
|
||||||
return cy.loginUser(synapse, user.username, user.password);
|
return cy.loginUser(synapse, user.username, user.password);
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
return cy.loginUser(synapse, user.username, user.password);
|
return cy.loginUser(synapse, user.username, user.password);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -45,50 +48,52 @@ describe("Device manager", () => {
|
||||||
|
|
||||||
it("should display sessions", () => {
|
it("should display sessions", () => {
|
||||||
cy.openUserSettings("Sessions");
|
cy.openUserSettings("Sessions");
|
||||||
cy.contains('Current session').should('exist');
|
cy.contains("Current session").should("exist");
|
||||||
|
|
||||||
cy.get('[data-testid="current-session-section"]').within(() => {
|
cy.get('[data-testid="current-session-section"]').within(() => {
|
||||||
cy.contains('Unverified session').should('exist');
|
cy.contains("Unverified session").should("exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
// current session details opened
|
// current session details opened
|
||||||
cy.get('[data-testid="current-session-toggle-details"]').click();
|
cy.get('[data-testid="current-session-toggle-details"]').click();
|
||||||
cy.contains('Session details').should('exist');
|
cy.contains("Session details").should("exist");
|
||||||
|
|
||||||
// close current session details
|
// close current session details
|
||||||
cy.get('[data-testid="current-session-toggle-details"]').click();
|
cy.get('[data-testid="current-session-toggle-details"]').click();
|
||||||
cy.contains('Session details').should('not.exist');
|
cy.contains("Session details").should("not.exist");
|
||||||
|
|
||||||
cy.get('[data-testid="security-recommendations-section"]').within(() => {
|
cy.get('[data-testid="security-recommendations-section"]').within(() => {
|
||||||
cy.contains('Security recommendations').should('exist');
|
cy.contains("Security recommendations").should("exist");
|
||||||
cy.get('[data-testid="unverified-devices-cta"]').should('have.text', 'View all (3)').click();
|
cy.get('[data-testid="unverified-devices-cta"]').should("have.text", "View all (3)").click();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Other sessions section
|
* Other sessions section
|
||||||
*/
|
*/
|
||||||
cy.contains('Other sessions').should('exist');
|
cy.contains("Other sessions").should("exist");
|
||||||
// filter applied after clicking through from security recommendations
|
// filter applied after clicking through from security recommendations
|
||||||
cy.get('[aria-label="Filter devices"]').should('have.text', 'Show: Unverified');
|
cy.get('[aria-label="Filter devices"]').should("have.text", "Show: Unverified");
|
||||||
cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 3);
|
cy.get(".mx_FilteredDeviceList_list").find(".mx_FilteredDeviceList_listItem").should("have.length", 3);
|
||||||
|
|
||||||
// select two sessions
|
// select two sessions
|
||||||
cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox').first().click();
|
cy.get(".mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox").first().click();
|
||||||
cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox').last().click();
|
cy.get(".mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox").last().click();
|
||||||
// sign out from list selection action buttons
|
// sign out from list selection action buttons
|
||||||
cy.get('[data-testid="sign-out-selection-cta"]').click();
|
cy.get('[data-testid="sign-out-selection-cta"]').click();
|
||||||
cy.get('[data-testid="dialog-primary-button"]').click();
|
cy.get('[data-testid="dialog-primary-button"]').click();
|
||||||
// list updated after sign out
|
// list updated after sign out
|
||||||
cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1);
|
cy.get(".mx_FilteredDeviceList_list").find(".mx_FilteredDeviceList_listItem").should("have.length", 1);
|
||||||
// security recommendation count updated
|
// security recommendation count updated
|
||||||
cy.get('[data-testid="unverified-devices-cta"]').should('have.text', 'View all (1)');
|
cy.get('[data-testid="unverified-devices-cta"]').should("have.text", "View all (1)");
|
||||||
|
|
||||||
const sessionName = `Alice's device`;
|
const sessionName = `Alice's device`;
|
||||||
// open the first session
|
// open the first session
|
||||||
cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem').first().within(() => {
|
cy.get(".mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem")
|
||||||
|
.first()
|
||||||
|
.within(() => {
|
||||||
cy.get('[aria-label="Show details"]').click();
|
cy.get('[aria-label="Show details"]').click();
|
||||||
|
|
||||||
cy.contains('Session details').should('exist');
|
cy.contains("Session details").should("exist");
|
||||||
|
|
||||||
cy.get('[data-testid="device-heading-rename-cta"]').click();
|
cy.get('[data-testid="device-heading-rename-cta"]').click();
|
||||||
cy.get('[data-testid="device-rename-input"]').type(sessionName);
|
cy.get('[data-testid="device-rename-input"]').type(sessionName);
|
||||||
|
@ -99,9 +104,9 @@ describe("Device manager", () => {
|
||||||
cy.get(".mx_Spinner").should("not.exist");
|
cy.get(".mx_Spinner").should("not.exist");
|
||||||
|
|
||||||
// session name updated in details
|
// session name updated in details
|
||||||
cy.get('.mx_DeviceDetailHeading h3').should('have.text', sessionName);
|
cy.get(".mx_DeviceDetailHeading h3").should("have.text", sessionName);
|
||||||
// and main list item
|
// and main list item
|
||||||
cy.get('.mx_DeviceTile h4').should('have.text', sessionName);
|
cy.get(".mx_DeviceTile h4").should("have.text", sessionName);
|
||||||
|
|
||||||
// sign out using the device details sign out
|
// sign out using the device details sign out
|
||||||
cy.get('[data-testid="device-detail-sign-out-cta"]').click();
|
cy.get('[data-testid="device-detail-sign-out-cta"]').click();
|
||||||
|
@ -110,7 +115,7 @@ describe("Device manager", () => {
|
||||||
cy.get('[data-testid="dialog-primary-button"]').click();
|
cy.get('[data-testid="dialog-primary-button"]').click();
|
||||||
|
|
||||||
// no other sessions or security recommendations sections when only one session
|
// no other sessions or security recommendations sections when only one session
|
||||||
cy.contains('Other sessions').should('not.exist');
|
cy.contains("Other sessions").should("not.exist");
|
||||||
cy.get('[data-testid="security-recommendations-section"]').should('not.exist');
|
cy.get('[data-testid="security-recommendations-section"]').should("not.exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { SynapseInstance } from "../../plugins/synapsedocker";
|
||||||
function seedLabs(synapse: SynapseInstance, labsVal: boolean | null): void {
|
function seedLabs(synapse: SynapseInstance, labsVal: boolean | null): void {
|
||||||
cy.initTestUser(synapse, "Sally", () => {
|
cy.initTestUser(synapse, "Sally", () => {
|
||||||
// seed labs flag
|
// seed labs flag
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
if (typeof labsVal === "boolean") {
|
if (typeof labsVal === "boolean") {
|
||||||
// stringify boolean
|
// stringify boolean
|
||||||
win.localStorage.setItem("mx_labs_feature_feature_hidden_read_receipts", `${labsVal}`);
|
win.localStorage.setItem("mx_labs_feature_feature_hidden_read_receipts", `${labsVal}`);
|
||||||
|
@ -64,7 +64,7 @@ describe("Hidden Read Receipts Setting Migration", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -73,17 +73,17 @@ describe("Hidden Read Receipts Setting Migration", () => {
|
||||||
cy.stopSynapse(synapse);
|
cy.stopSynapse(synapse);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not migrate the lack of a labs flag', () => {
|
it("should not migrate the lack of a labs flag", () => {
|
||||||
seedLabs(synapse, null);
|
seedLabs(synapse, null);
|
||||||
testForVal(null);
|
testForVal(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate labsHiddenRR=false as sendRR=true', () => {
|
it("should migrate labsHiddenRR=false as sendRR=true", () => {
|
||||||
seedLabs(synapse, false);
|
seedLabs(synapse, false);
|
||||||
testForVal(true);
|
testForVal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate labsHiddenRR=true as sendRR=false', () => {
|
it("should migrate labsHiddenRR=true as sendRR=false", () => {
|
||||||
seedLabs(synapse, true);
|
seedLabs(synapse, true);
|
||||||
testForVal(false);
|
testForVal(false);
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,18 +26,17 @@ import { ProxyInstance } from "../../plugins/sliding-sync";
|
||||||
|
|
||||||
describe("Sliding Sync", () => {
|
describe("Sliding Sync", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").as("synapse").then(synapse => {
|
cy.startSynapse("default")
|
||||||
|
.as("synapse")
|
||||||
|
.then((synapse) => {
|
||||||
cy.startProxy(synapse).as("proxy");
|
cy.startProxy(synapse).as("proxy");
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.all([
|
cy.all([cy.get<SynapseInstance>("@synapse"), cy.get<ProxyInstance>("@proxy")]).then(([synapse, proxy]) => {
|
||||||
cy.get<SynapseInstance>("@synapse"),
|
|
||||||
cy.get<ProxyInstance>("@proxy"),
|
|
||||||
]).then(([synapse, proxy]) => {
|
|
||||||
cy.enableLabsFeature("feature_sliding_sync");
|
cy.enableLabsFeature("feature_sliding_sync");
|
||||||
|
|
||||||
cy.intercept("/config.json?cachebuster=*", req => {
|
cy.intercept("/config.json?cachebuster=*", (req) => {
|
||||||
return req.continue(res => {
|
return req.continue((res) => {
|
||||||
res.send(200, {
|
res.send(200, {
|
||||||
...res.body,
|
...res.body,
|
||||||
setting_defaults: {
|
setting_defaults: {
|
||||||
|
@ -62,10 +61,15 @@ describe("Sliding Sync", () => {
|
||||||
|
|
||||||
// assert order
|
// assert order
|
||||||
const checkOrder = (wantOrder: string[]) => {
|
const checkOrder = (wantOrder: string[]) => {
|
||||||
cy.contains(".mx_RoomSublist", "Rooms").find(".mx_RoomTile_title").should((elements) => {
|
cy.contains(".mx_RoomSublist", "Rooms")
|
||||||
expect(_.map(elements, (e) => {
|
.find(".mx_RoomTile_title")
|
||||||
|
.should((elements) => {
|
||||||
|
expect(
|
||||||
|
_.map(elements, (e) => {
|
||||||
return e.textContent;
|
return e.textContent;
|
||||||
}), "rooms are sorted").to.deep.equal(wantOrder);
|
}),
|
||||||
|
"rooms are sorted",
|
||||||
|
).to.deep.equal(wantOrder);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const bumpRoom = (alias: string) => {
|
const bumpRoom = (alias: string) => {
|
||||||
|
@ -80,9 +84,11 @@ describe("Sliding Sync", () => {
|
||||||
const createAndJoinBob = () => {
|
const createAndJoinBob = () => {
|
||||||
// create a Bob user
|
// create a Bob user
|
||||||
cy.get<SynapseInstance>("@synapse").then((synapse) => {
|
cy.get<SynapseInstance>("@synapse").then((synapse) => {
|
||||||
return cy.getBot(synapse, {
|
return cy
|
||||||
|
.getBot(synapse, {
|
||||||
displayName: "Bob",
|
displayName: "Bob",
|
||||||
}).as("bob");
|
})
|
||||||
|
.as("bob");
|
||||||
});
|
});
|
||||||
|
|
||||||
// invite Bob to Test Room and accept then send a message.
|
// invite Bob to Test Room and accept then send a message.
|
||||||
|
@ -95,7 +101,7 @@ describe("Sliding Sync", () => {
|
||||||
|
|
||||||
// sanity check everything works
|
// sanity check everything works
|
||||||
it("should correctly render expected messages", () => {
|
it("should correctly render expected messages", () => {
|
||||||
cy.get<string>("@roomId").then(roomId => cy.visit("/#/room/" + roomId));
|
cy.get<string>("@roomId").then((roomId) => cy.visit("/#/room/" + roomId));
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
|
|
||||||
// Wait until configuration is finished
|
// Wait until configuration is finished
|
||||||
|
@ -114,54 +120,52 @@ describe("Sliding Sync", () => {
|
||||||
cy.createRoom({ name: "Pineapple" }).then(() => cy.contains(".mx_RoomSublist", "Pineapple"));
|
cy.createRoom({ name: "Pineapple" }).then(() => cy.contains(".mx_RoomSublist", "Pineapple"));
|
||||||
cy.createRoom({ name: "Orange" }).then(() => cy.contains(".mx_RoomSublist", "Orange"));
|
cy.createRoom({ name: "Orange" }).then(() => cy.contains(".mx_RoomSublist", "Orange"));
|
||||||
// check the rooms are in the right order
|
// check the rooms are in the right order
|
||||||
cy.get(".mx_RoomTile").should('have.length', 4); // due to the Test Room in beforeEach
|
cy.get(".mx_RoomTile").should("have.length", 4); // due to the Test Room in beforeEach
|
||||||
checkOrder([
|
checkOrder(["Orange", "Pineapple", "Apple", "Test Room"]);
|
||||||
"Orange", "Pineapple", "Apple", "Test Room",
|
|
||||||
]);
|
|
||||||
|
|
||||||
cy.contains(".mx_RoomSublist", "Rooms").find(".mx_RoomSublist_menuButton").click({ force: true });
|
cy.contains(".mx_RoomSublist", "Rooms").find(".mx_RoomSublist_menuButton").click({ force: true });
|
||||||
cy.contains("A-Z").click();
|
cy.contains("A-Z").click();
|
||||||
cy.get('.mx_StyledRadioButton_checked').should("contain.text", "A-Z");
|
cy.get(".mx_StyledRadioButton_checked").should("contain.text", "A-Z");
|
||||||
checkOrder([
|
checkOrder(["Apple", "Orange", "Pineapple", "Test Room"]);
|
||||||
"Apple", "Orange", "Pineapple", "Test Room",
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should move rooms around as new events arrive", () => {
|
it("should move rooms around as new events arrive", () => {
|
||||||
// create rooms and check room names are correct
|
// create rooms and check room names are correct
|
||||||
cy.createRoom({ name: "Apple" }).as("roomA").then(() => cy.contains(".mx_RoomSublist", "Apple"));
|
cy.createRoom({ name: "Apple" })
|
||||||
cy.createRoom({ name: "Pineapple" }).as("roomP").then(() => cy.contains(".mx_RoomSublist", "Pineapple"));
|
.as("roomA")
|
||||||
cy.createRoom({ name: "Orange" }).as("roomO").then(() => cy.contains(".mx_RoomSublist", "Orange"));
|
.then(() => cy.contains(".mx_RoomSublist", "Apple"));
|
||||||
|
cy.createRoom({ name: "Pineapple" })
|
||||||
|
.as("roomP")
|
||||||
|
.then(() => cy.contains(".mx_RoomSublist", "Pineapple"));
|
||||||
|
cy.createRoom({ name: "Orange" })
|
||||||
|
.as("roomO")
|
||||||
|
.then(() => cy.contains(".mx_RoomSublist", "Orange"));
|
||||||
|
|
||||||
// Select the Test Room
|
// Select the Test Room
|
||||||
cy.contains(".mx_RoomTile", "Test Room").click();
|
cy.contains(".mx_RoomTile", "Test Room").click();
|
||||||
|
|
||||||
checkOrder([
|
checkOrder(["Orange", "Pineapple", "Apple", "Test Room"]);
|
||||||
"Orange", "Pineapple", "Apple", "Test Room",
|
|
||||||
]);
|
|
||||||
bumpRoom("@roomA");
|
bumpRoom("@roomA");
|
||||||
checkOrder([
|
checkOrder(["Apple", "Orange", "Pineapple", "Test Room"]);
|
||||||
"Apple", "Orange", "Pineapple", "Test Room",
|
|
||||||
]);
|
|
||||||
bumpRoom("@roomO");
|
bumpRoom("@roomO");
|
||||||
checkOrder([
|
checkOrder(["Orange", "Apple", "Pineapple", "Test Room"]);
|
||||||
"Orange", "Apple", "Pineapple", "Test Room",
|
|
||||||
]);
|
|
||||||
bumpRoom("@roomO");
|
bumpRoom("@roomO");
|
||||||
checkOrder([
|
checkOrder(["Orange", "Apple", "Pineapple", "Test Room"]);
|
||||||
"Orange", "Apple", "Pineapple", "Test Room",
|
|
||||||
]);
|
|
||||||
bumpRoom("@roomP");
|
bumpRoom("@roomP");
|
||||||
checkOrder([
|
checkOrder(["Pineapple", "Orange", "Apple", "Test Room"]);
|
||||||
"Pineapple", "Orange", "Apple", "Test Room",
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not move the selected room: it should be sticky", () => {
|
it("should not move the selected room: it should be sticky", () => {
|
||||||
// create rooms and check room names are correct
|
// create rooms and check room names are correct
|
||||||
cy.createRoom({ name: "Apple" }).as("roomA").then(() => cy.contains(".mx_RoomSublist", "Apple"));
|
cy.createRoom({ name: "Apple" })
|
||||||
cy.createRoom({ name: "Pineapple" }).as("roomP").then(() => cy.contains(".mx_RoomSublist", "Pineapple"));
|
.as("roomA")
|
||||||
cy.createRoom({ name: "Orange" }).as("roomO").then(() => cy.contains(".mx_RoomSublist", "Orange"));
|
.then(() => cy.contains(".mx_RoomSublist", "Apple"));
|
||||||
|
cy.createRoom({ name: "Pineapple" })
|
||||||
|
.as("roomP")
|
||||||
|
.then(() => cy.contains(".mx_RoomSublist", "Pineapple"));
|
||||||
|
cy.createRoom({ name: "Orange" })
|
||||||
|
.as("roomO")
|
||||||
|
.then(() => cy.contains(".mx_RoomSublist", "Orange"));
|
||||||
|
|
||||||
// Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should
|
// Given a list of Orange, Pineapple, Apple - if Pineapple is active and a message is sent in Apple, the list should
|
||||||
// turn into Apple, Pineapple, Orange - the index position of Pineapple never changes even though the list should technically
|
// turn into Apple, Pineapple, Orange - the index position of Pineapple never changes even though the list should technically
|
||||||
|
@ -169,23 +173,17 @@ describe("Sliding Sync", () => {
|
||||||
|
|
||||||
// Select the Pineapple room
|
// Select the Pineapple room
|
||||||
cy.contains(".mx_RoomTile", "Pineapple").click();
|
cy.contains(".mx_RoomTile", "Pineapple").click();
|
||||||
checkOrder([
|
checkOrder(["Orange", "Pineapple", "Apple", "Test Room"]);
|
||||||
"Orange", "Pineapple", "Apple", "Test Room",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Move Apple
|
// Move Apple
|
||||||
bumpRoom("@roomA");
|
bumpRoom("@roomA");
|
||||||
checkOrder([
|
checkOrder(["Apple", "Pineapple", "Orange", "Test Room"]);
|
||||||
"Apple", "Pineapple", "Orange", "Test Room",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Select the Test Room
|
// Select the Test Room
|
||||||
cy.contains(".mx_RoomTile", "Test Room").click();
|
cy.contains(".mx_RoomTile", "Test Room").click();
|
||||||
|
|
||||||
// the rooms reshuffle to match reality
|
// the rooms reshuffle to match reality
|
||||||
checkOrder([
|
checkOrder(["Apple", "Orange", "Pineapple", "Test Room"]);
|
||||||
"Apple", "Orange", "Pineapple", "Test Room",
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show the right unread notifications", () => {
|
it("should show the right unread notifications", () => {
|
||||||
|
@ -212,7 +210,8 @@ describe("Sliding Sync", () => {
|
||||||
cy.contains(".mx_RoomTile", "Test Room").should("not.have.class", "mx_NotificationBadge_count");
|
cy.contains(".mx_RoomTile", "Test Room").should("not.have.class", "mx_NotificationBadge_count");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show unread indicators", () => { // TODO: for now. Later we should.
|
it("should not show unread indicators", () => {
|
||||||
|
// TODO: for now. Later we should.
|
||||||
createAndJoinBob();
|
createAndJoinBob();
|
||||||
|
|
||||||
// disable notifs in this room (TODO: CS API call?)
|
// disable notifs in this room (TODO: CS API call?)
|
||||||
|
@ -223,17 +222,13 @@ describe("Sliding Sync", () => {
|
||||||
cy.createRoom({
|
cy.createRoom({
|
||||||
name: "Dummy",
|
name: "Dummy",
|
||||||
});
|
});
|
||||||
checkOrder([
|
checkOrder(["Dummy", "Test Room"]);
|
||||||
"Dummy", "Test Room",
|
|
||||||
]);
|
|
||||||
|
|
||||||
cy.all([cy.get<string>("@roomId"), cy.get<MatrixClient>("@bob")]).then(([roomId, bob]) => {
|
cy.all([cy.get<string>("@roomId"), cy.get<MatrixClient>("@bob")]).then(([roomId, bob]) => {
|
||||||
return bob.sendTextMessage(roomId, "Do you read me?");
|
return bob.sendTextMessage(roomId, "Do you read me?");
|
||||||
});
|
});
|
||||||
// wait for this message to arrive, tell by the room list resorting
|
// wait for this message to arrive, tell by the room list resorting
|
||||||
checkOrder([
|
checkOrder(["Test Room", "Dummy"]);
|
||||||
"Test Room", "Dummy",
|
|
||||||
]);
|
|
||||||
|
|
||||||
cy.contains(".mx_RoomTile", "Test Room").get(".mx_NotificationBadge").should("not.exist");
|
cy.contains(".mx_RoomTile", "Test Room").get(".mx_NotificationBadge").should("not.exist");
|
||||||
});
|
});
|
||||||
|
@ -242,13 +237,18 @@ describe("Sliding Sync", () => {
|
||||||
cy.get(".mx_UserMenu_userAvatar").click();
|
cy.get(".mx_UserMenu_userAvatar").click();
|
||||||
cy.contains("All settings").click();
|
cy.contains("All settings").click();
|
||||||
cy.contains("Preferences").click();
|
cy.contains("Preferences").click();
|
||||||
cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format").should("exist").find(
|
cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format")
|
||||||
".mx_ToggleSwitch_on").should("not.exist");
|
.should("exist")
|
||||||
cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format").should("exist").find(
|
.find(".mx_ToggleSwitch_on")
|
||||||
".mx_ToggleSwitch_ball").click();
|
.should("not.exist");
|
||||||
cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format", { timeout: 2000 }).should("exist").find(
|
cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format")
|
||||||
".mx_ToggleSwitch_on", { timeout: 2000 },
|
.should("exist")
|
||||||
).should("exist");
|
.find(".mx_ToggleSwitch_ball")
|
||||||
|
.click();
|
||||||
|
cy.contains(".mx_SettingsFlag", "Show timestamps in 12 hour format", { timeout: 2000 })
|
||||||
|
.should("exist")
|
||||||
|
.find(".mx_ToggleSwitch_on", { timeout: 2000 })
|
||||||
|
.should("exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show and be able to accept/reject/rescind invites", () => {
|
it("should show and be able to accept/reject/rescind invites", () => {
|
||||||
|
@ -263,15 +263,20 @@ describe("Sliding Sync", () => {
|
||||||
// - roomJoin: will join this room
|
// - roomJoin: will join this room
|
||||||
// - roomReject: will reject the invite
|
// - roomReject: will reject the invite
|
||||||
// - roomRescind: will make Bob rescind the invite
|
// - roomRescind: will make Bob rescind the invite
|
||||||
let roomJoin; let roomReject; let roomRescind; let bobClient;
|
let roomJoin;
|
||||||
cy.get<MatrixClient>("@bob").then((bob) => {
|
let roomReject;
|
||||||
|
let roomRescind;
|
||||||
|
let bobClient;
|
||||||
|
cy.get<MatrixClient>("@bob")
|
||||||
|
.then((bob) => {
|
||||||
bobClient = bob;
|
bobClient = bob;
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
bob.createRoom({ name: "Join" }),
|
bob.createRoom({ name: "Join" }),
|
||||||
bob.createRoom({ name: "Reject" }),
|
bob.createRoom({ name: "Reject" }),
|
||||||
bob.createRoom({ name: "Rescind" }),
|
bob.createRoom({ name: "Rescind" }),
|
||||||
]);
|
]);
|
||||||
}).then(([join, reject, rescind]) => {
|
})
|
||||||
|
.then(([join, reject, rescind]) => {
|
||||||
roomJoin = join.room_id;
|
roomJoin = join.room_id;
|
||||||
roomReject = reject.room_id;
|
roomReject = reject.room_id;
|
||||||
roomRescind = rescind.room_id;
|
roomRescind = rescind.room_id;
|
||||||
|
@ -283,29 +288,30 @@ describe("Sliding Sync", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// wait for them all to be on the UI
|
// wait for them all to be on the UI
|
||||||
cy.get(".mx_RoomTile").should('have.length', 4); // due to the Test Room in beforeEach
|
cy.get(".mx_RoomTile").should("have.length", 4); // due to the Test Room in beforeEach
|
||||||
|
|
||||||
cy.contains(".mx_RoomTile", "Join").click();
|
cy.contains(".mx_RoomTile", "Join").click();
|
||||||
cy.contains(".mx_AccessibleButton", "Accept").click();
|
cy.contains(".mx_AccessibleButton", "Accept").click();
|
||||||
|
|
||||||
checkOrder([
|
checkOrder(["Join", "Test Room"]);
|
||||||
"Join", "Test Room",
|
|
||||||
]);
|
|
||||||
|
|
||||||
cy.contains(".mx_RoomTile", "Reject").click();
|
cy.contains(".mx_RoomTile", "Reject").click();
|
||||||
cy.contains(".mx_RoomView .mx_AccessibleButton", "Reject").click();
|
cy.contains(".mx_RoomView .mx_AccessibleButton", "Reject").click();
|
||||||
|
|
||||||
// wait for the rejected room to disappear
|
// wait for the rejected room to disappear
|
||||||
cy.get(".mx_RoomTile").should('have.length', 3);
|
cy.get(".mx_RoomTile").should("have.length", 3);
|
||||||
|
|
||||||
// check the lists are correct
|
// check the lists are correct
|
||||||
checkOrder([
|
checkOrder(["Join", "Test Room"]);
|
||||||
"Join", "Test Room",
|
cy.contains(".mx_RoomSublist", "Invites")
|
||||||
]);
|
.find(".mx_RoomTile_title")
|
||||||
cy.contains(".mx_RoomSublist", "Invites").find(".mx_RoomTile_title").should((elements) => {
|
.should((elements) => {
|
||||||
expect(_.map(elements, (e) => {
|
expect(
|
||||||
|
_.map(elements, (e) => {
|
||||||
return e.textContent;
|
return e.textContent;
|
||||||
}), "rooms are sorted").to.deep.equal(["Rescind"]);
|
}),
|
||||||
|
"rooms are sorted",
|
||||||
|
).to.deep.equal(["Rescind"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// now rescind the invite
|
// now rescind the invite
|
||||||
|
@ -314,18 +320,18 @@ describe("Sliding Sync", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// wait for the rescind to take effect and check the joined list once more
|
// wait for the rescind to take effect and check the joined list once more
|
||||||
cy.get(".mx_RoomTile").should('have.length', 2);
|
cy.get(".mx_RoomTile").should("have.length", 2);
|
||||||
checkOrder([
|
checkOrder(["Join", "Test Room"]);
|
||||||
"Join", "Test Room",
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show a favourite DM only in the favourite sublist", () => {
|
it("should show a favourite DM only in the favourite sublist", () => {
|
||||||
cy.createRoom({
|
cy.createRoom({
|
||||||
name: "Favourite DM",
|
name: "Favourite DM",
|
||||||
is_direct: true,
|
is_direct: true,
|
||||||
}).as("room").then(roomId => {
|
})
|
||||||
cy.getClient().then(cli => cli.setRoomTag(roomId, "m.favourite", { order: 0.5 }));
|
.as("room")
|
||||||
|
.then((roomId) => {
|
||||||
|
cy.getClient().then((cli) => cli.setRoomTag(roomId, "m.favourite", { order: 0.5 }));
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.contains('.mx_RoomSublist[aria-label="Favourites"] .mx_RoomTile', "Favourite DM").should("exist");
|
cy.contains('.mx_RoomSublist[aria-label="Favourites"] .mx_RoomTile', "Favourite DM").should("exist");
|
||||||
|
@ -335,7 +341,9 @@ describe("Sliding Sync", () => {
|
||||||
// Regression test for a bug in SS mode, but would be useful to have in non-SS mode too.
|
// Regression test for a bug in SS mode, but would be useful to have in non-SS mode too.
|
||||||
// This ensures we are setting RoomViewStore state correctly.
|
// This ensures we are setting RoomViewStore state correctly.
|
||||||
it("should clear the reply to field when swapping rooms", () => {
|
it("should clear the reply to field when swapping rooms", () => {
|
||||||
cy.createRoom({ name: "Other Room" }).as("roomA").then(() => cy.contains(".mx_RoomSublist", "Other Room"));
|
cy.createRoom({ name: "Other Room" })
|
||||||
|
.as("roomA")
|
||||||
|
.then(() => cy.contains(".mx_RoomSublist", "Other Room"));
|
||||||
cy.get<string>("@roomId").then((roomId) => {
|
cy.get<string>("@roomId").then((roomId) => {
|
||||||
return cy.sendEvent(roomId, null, "m.room.message", {
|
return cy.sendEvent(roomId, null, "m.room.message", {
|
||||||
body: "Hello world",
|
body: "Hello world",
|
||||||
|
@ -346,9 +354,9 @@ describe("Sliding Sync", () => {
|
||||||
cy.contains(".mx_RoomTile", "Test Room").click();
|
cy.contains(".mx_RoomTile", "Test Room").click();
|
||||||
cy.get(".mx_ReplyPreview").should("not.exist");
|
cy.get(".mx_ReplyPreview").should("not.exist");
|
||||||
// click reply-to on the Hello World message
|
// click reply-to on the Hello World message
|
||||||
cy.contains(".mx_EventTile", "Hello world").find('.mx_AccessibleButton[aria-label="Reply"]').click(
|
cy.contains(".mx_EventTile", "Hello world")
|
||||||
{ force: true },
|
.find('.mx_AccessibleButton[aria-label="Reply"]')
|
||||||
);
|
.click({ force: true });
|
||||||
// check it's visible
|
// check it's visible
|
||||||
cy.get(".mx_ReplyPreview").should("exist");
|
cy.get(".mx_ReplyPreview").should("exist");
|
||||||
// now click Other Room
|
// now click Other Room
|
||||||
|
@ -365,15 +373,18 @@ describe("Sliding Sync", () => {
|
||||||
it("should not cancel replies when permalinks are clicked ", () => {
|
it("should not cancel replies when permalinks are clicked ", () => {
|
||||||
cy.get<string>("@roomId").then((roomId) => {
|
cy.get<string>("@roomId").then((roomId) => {
|
||||||
// we require a first message as you cannot click the permalink text with the avatar in the way
|
// we require a first message as you cannot click the permalink text with the avatar in the way
|
||||||
return cy.sendEvent(roomId, null, "m.room.message", {
|
return cy
|
||||||
|
.sendEvent(roomId, null, "m.room.message", {
|
||||||
body: "First message",
|
body: "First message",
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
return cy.sendEvent(roomId, null, "m.room.message", {
|
return cy.sendEvent(roomId, null, "m.room.message", {
|
||||||
body: "Permalink me",
|
body: "Permalink me",
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
});
|
});
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.sendEvent(roomId, null, "m.room.message", {
|
cy.sendEvent(roomId, null, "m.room.message", {
|
||||||
body: "Reply to me",
|
body: "Reply to me",
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
|
@ -384,9 +395,9 @@ describe("Sliding Sync", () => {
|
||||||
cy.contains(".mx_RoomTile", "Test Room").click();
|
cy.contains(".mx_RoomTile", "Test Room").click();
|
||||||
cy.get(".mx_ReplyPreview").should("not.exist");
|
cy.get(".mx_ReplyPreview").should("not.exist");
|
||||||
// click reply-to on the Reply to me message
|
// click reply-to on the Reply to me message
|
||||||
cy.contains(".mx_EventTile", "Reply to me").find('.mx_AccessibleButton[aria-label="Reply"]').click(
|
cy.contains(".mx_EventTile", "Reply to me")
|
||||||
{ force: true },
|
.find('.mx_AccessibleButton[aria-label="Reply"]')
|
||||||
);
|
.click({ force: true });
|
||||||
// check it's visible
|
// check it's visible
|
||||||
cy.get(".mx_ReplyPreview").should("exist");
|
cy.get(".mx_ReplyPreview").should("exist");
|
||||||
// now click on the permalink for Permalink me
|
// now click on the permalink for Permalink me
|
||||||
|
|
|
@ -37,12 +37,14 @@ function spaceCreateOptions(spaceName: string): ICreateRoomOpts {
|
||||||
creation_content: {
|
creation_content: {
|
||||||
type: "m.space",
|
type: "m.space",
|
||||||
},
|
},
|
||||||
initial_state: [{
|
initial_state: [
|
||||||
|
{
|
||||||
type: "m.room.name",
|
type: "m.room.name",
|
||||||
content: {
|
content: {
|
||||||
name: spaceName,
|
name: spaceName,
|
||||||
},
|
},
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +63,10 @@ describe("Spaces", () => {
|
||||||
let user: UserCredentials;
|
let user: UserCredentials;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Sue").then(_user => {
|
cy.initTestUser(synapse, "Sue").then((_user) => {
|
||||||
user = _user;
|
user = _user;
|
||||||
cy.mockClipboard();
|
cy.mockClipboard();
|
||||||
});
|
});
|
||||||
|
@ -78,8 +80,10 @@ describe("Spaces", () => {
|
||||||
it("should allow user to create public space", () => {
|
it("should allow user to create public space", () => {
|
||||||
openSpaceCreateMenu().within(() => {
|
openSpaceCreateMenu().within(() => {
|
||||||
cy.get(".mx_SpaceCreateMenuType_public").click();
|
cy.get(".mx_SpaceCreateMenuType_public").click();
|
||||||
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
|
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile(
|
||||||
.selectFile("cypress/fixtures/riot.png", { force: true });
|
"cypress/fixtures/riot.png",
|
||||||
|
{ force: true },
|
||||||
|
);
|
||||||
cy.get('input[label="Name"]').type("Let's have a Riot");
|
cy.get('input[label="Name"]').type("Let's have a Riot");
|
||||||
cy.get('input[label="Address"]').should("have.value", "lets-have-a-riot");
|
cy.get('input[label="Address"]').should("have.value", "lets-have-a-riot");
|
||||||
cy.get('textarea[label="Description"]').type("This is a space to reminisce Riot.im!");
|
cy.get('textarea[label="Description"]').type("This is a space to reminisce Riot.im!");
|
||||||
|
@ -108,8 +112,10 @@ describe("Spaces", () => {
|
||||||
it("should allow user to create private space", () => {
|
it("should allow user to create private space", () => {
|
||||||
openSpaceCreateMenu().within(() => {
|
openSpaceCreateMenu().within(() => {
|
||||||
cy.get(".mx_SpaceCreateMenuType_private").click();
|
cy.get(".mx_SpaceCreateMenuType_private").click();
|
||||||
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
|
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile(
|
||||||
.selectFile("cypress/fixtures/riot.png", { force: true });
|
"cypress/fixtures/riot.png",
|
||||||
|
{ force: true },
|
||||||
|
);
|
||||||
cy.get('input[label="Name"]').type("This is not a Riot");
|
cy.get('input[label="Name"]').type("This is not a Riot");
|
||||||
cy.get('input[label="Address"]').should("not.exist");
|
cy.get('input[label="Address"]').should("not.exist");
|
||||||
cy.get('textarea[label="Description"]').type("This is a private space of mourning Riot.im...");
|
cy.get('textarea[label="Description"]').type("This is a private space of mourning Riot.im...");
|
||||||
|
@ -145,8 +151,10 @@ describe("Spaces", () => {
|
||||||
|
|
||||||
openSpaceCreateMenu().within(() => {
|
openSpaceCreateMenu().within(() => {
|
||||||
cy.get(".mx_SpaceCreateMenuType_private").click();
|
cy.get(".mx_SpaceCreateMenuType_private").click();
|
||||||
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
|
cy.get('.mx_SpaceBasicSettings_avatarContainer input[type="file"]').selectFile(
|
||||||
.selectFile("cypress/fixtures/riot.png", { force: true });
|
"cypress/fixtures/riot.png",
|
||||||
|
{ force: true },
|
||||||
|
);
|
||||||
cy.get('input[label="Address"]').should("not.exist");
|
cy.get('input[label="Address"]').should("not.exist");
|
||||||
cy.get('textarea[label="Description"]').type("This is a personal space to mourn Riot.im...");
|
cy.get('textarea[label="Description"]').type("This is a personal space to mourn Riot.im...");
|
||||||
cy.get('input[label="Name"]').type("This is my Riot{enter}");
|
cy.get('input[label="Name"]').type("This is my Riot{enter}");
|
||||||
|
@ -163,7 +171,7 @@ describe("Spaces", () => {
|
||||||
|
|
||||||
it("should allow user to invite another to a space", () => {
|
it("should allow user to invite another to a space", () => {
|
||||||
let bot: MatrixClient;
|
let bot: MatrixClient;
|
||||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
|
||||||
bot = _bot;
|
bot = _bot;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -198,13 +206,17 @@ describe("Spaces", () => {
|
||||||
});
|
});
|
||||||
cy.getSpacePanelButton("My Space").should("exist");
|
cy.getSpacePanelButton("My Space").should("exist");
|
||||||
|
|
||||||
cy.getBot(synapse, { displayName: "BotBob" }).then({ timeout: 10000 }, async bot => {
|
cy.getBot(synapse, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => {
|
||||||
const { room_id: roomId } = await bot.createRoom(spaceCreateOptions("Space Space"));
|
const { room_id: roomId } = await bot.createRoom(spaceCreateOptions("Space Space"));
|
||||||
await bot.invite(roomId, user.userId);
|
await bot.invite(roomId, user.userId);
|
||||||
});
|
});
|
||||||
// Assert that `Space Space` is above `My Space` due to it being an invite
|
// Assert that `Space Space` is above `My Space` due to it being an invite
|
||||||
cy.getSpacePanelButton("Space Space").should("exist")
|
cy.getSpacePanelButton("Space Space")
|
||||||
.parent().next().find('.mx_SpaceButton[aria-label="My Space"]').should("exist");
|
.should("exist")
|
||||||
|
.parent()
|
||||||
|
.next()
|
||||||
|
.find('.mx_SpaceButton[aria-label="My Space"]')
|
||||||
|
.should("exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should include rooms in space home", () => {
|
it("should include rooms in space home", () => {
|
||||||
|
@ -216,16 +228,10 @@ describe("Spaces", () => {
|
||||||
}).as("roomId2");
|
}).as("roomId2");
|
||||||
|
|
||||||
const spaceName = "Spacey Mc. Space Space";
|
const spaceName = "Spacey Mc. Space Space";
|
||||||
cy.all([
|
cy.all([cy.get<string>("@roomId1"), cy.get<string>("@roomId2")]).then(([roomId1, roomId2]) => {
|
||||||
cy.get<string>("@roomId1"),
|
|
||||||
cy.get<string>("@roomId2"),
|
|
||||||
]).then(([roomId1, roomId2]) => {
|
|
||||||
cy.createSpace({
|
cy.createSpace({
|
||||||
name: spaceName,
|
name: spaceName,
|
||||||
initial_state: [
|
initial_state: [spaceChildInitialState(roomId1), spaceChildInitialState(roomId2)],
|
||||||
spaceChildInitialState(roomId1),
|
|
||||||
spaceChildInitialState(roomId2),
|
|
||||||
],
|
|
||||||
}).as("spaceId");
|
}).as("spaceId");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -244,12 +250,10 @@ describe("Spaces", () => {
|
||||||
cy.createSpace({
|
cy.createSpace({
|
||||||
name: "Child Space",
|
name: "Child Space",
|
||||||
initial_state: [],
|
initial_state: [],
|
||||||
}).then(spaceId => {
|
}).then((spaceId) => {
|
||||||
cy.createSpace({
|
cy.createSpace({
|
||||||
name: "Root Space",
|
name: "Root Space",
|
||||||
initial_state: [
|
initial_state: [spaceChildInitialState(spaceId)],
|
||||||
spaceChildInitialState(spaceId),
|
|
||||||
],
|
|
||||||
}).as("spaceId");
|
}).as("spaceId");
|
||||||
});
|
});
|
||||||
cy.get('.mx_SpacePanel .mx_SpaceButton[aria-label="Root Space"]').should("exist");
|
cy.get('.mx_SpacePanel .mx_SpaceButton[aria-label="Root Space"]').should("exist");
|
||||||
|
@ -258,7 +262,7 @@ describe("Spaces", () => {
|
||||||
const axeOptions = {
|
const axeOptions = {
|
||||||
rules: {
|
rules: {
|
||||||
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
|
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
|
||||||
'nested-interactive': {
|
"nested-interactive": {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -269,8 +273,10 @@ describe("Spaces", () => {
|
||||||
cy.get(".mx_SpaceButton_toggleCollapse").click({ force: true });
|
cy.get(".mx_SpaceButton_toggleCollapse").click({ force: true });
|
||||||
cy.get(".mx_SpacePanel:not(.collapsed)").should("exist");
|
cy.get(".mx_SpacePanel:not(.collapsed)").should("exist");
|
||||||
|
|
||||||
cy.contains(".mx_SpaceItem", "Root Space").should("exist")
|
cy.contains(".mx_SpaceItem", "Root Space")
|
||||||
.contains(".mx_SpaceItem", "Child Space").should("exist");
|
.should("exist")
|
||||||
|
.contains(".mx_SpaceItem", "Child Space")
|
||||||
|
.should("exist");
|
||||||
|
|
||||||
cy.checkA11y(undefined, axeOptions);
|
cy.checkA11y(undefined, axeOptions);
|
||||||
cy.get(".mx_SpacePanel").percySnapshotElement("Space panel expanded", { widths: [258] });
|
cy.get(".mx_SpacePanel").percySnapshotElement("Space panel expanded", { widths: [258] });
|
||||||
|
|
|
@ -26,7 +26,7 @@ import Shadow = Cypress.Shadow;
|
||||||
|
|
||||||
export enum Filter {
|
export enum Filter {
|
||||||
People = "people",
|
People = "people",
|
||||||
PublicRooms = "public_rooms"
|
PublicRooms = "public_rooms",
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -37,43 +37,47 @@ declare global {
|
||||||
* Opens the spotlight dialog
|
* Opens the spotlight dialog
|
||||||
*/
|
*/
|
||||||
openSpotlightDialog(
|
openSpotlightDialog(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
): Chainable<JQuery<HTMLElement>>;
|
): Chainable<JQuery<HTMLElement>>;
|
||||||
spotlightDialog(
|
spotlightDialog(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
): Chainable<JQuery<HTMLElement>>;
|
): Chainable<JQuery<HTMLElement>>;
|
||||||
spotlightFilter(
|
spotlightFilter(
|
||||||
filter: Filter | null,
|
filter: Filter | null,
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
): Chainable<JQuery<HTMLElement>>;
|
): Chainable<JQuery<HTMLElement>>;
|
||||||
spotlightSearch(
|
spotlightSearch(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
): Chainable<JQuery<HTMLElement>>;
|
): Chainable<JQuery<HTMLElement>>;
|
||||||
spotlightResults(
|
spotlightResults(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
): Chainable<JQuery<HTMLElement>>;
|
): Chainable<JQuery<HTMLElement>>;
|
||||||
roomHeaderName(
|
roomHeaderName(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
): Chainable<JQuery<HTMLElement>>;
|
): Chainable<JQuery<HTMLElement>>;
|
||||||
startDM(name: string): Chainable<void>;
|
startDM(name: string): Chainable<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("openSpotlightDialog", (
|
Cypress.Commands.add(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
"openSpotlightDialog",
|
||||||
): Chainable<JQuery<HTMLElement>> => {
|
(options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<HTMLElement>> => {
|
||||||
cy.get('.mx_RoomSearch_spotlightTrigger', options).click({ force: true });
|
cy.get(".mx_RoomSearch_spotlightTrigger", options).click({ force: true });
|
||||||
return cy.spotlightDialog(options);
|
return cy.spotlightDialog(options);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("spotlightDialog", (
|
Cypress.Commands.add(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
"spotlightDialog",
|
||||||
): Chainable<JQuery<HTMLElement>> => {
|
(options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<HTMLElement>> => {
|
||||||
return cy.get('[role=dialog][aria-label="Search Dialog"]', options);
|
return cy.get('[role=dialog][aria-label="Search Dialog"]', options);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("spotlightFilter", (
|
Cypress.Commands.add(
|
||||||
|
"spotlightFilter",
|
||||||
|
(
|
||||||
filter: Filter | null,
|
filter: Filter | null,
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
): Chainable<JQuery<HTMLElement>> => {
|
): Chainable<JQuery<HTMLElement>> => {
|
||||||
|
@ -90,25 +94,29 @@ Cypress.Commands.add("spotlightFilter", (
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return cy.get(selector, options).click();
|
return cy.get(selector, options).click();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("spotlightSearch", (
|
Cypress.Commands.add(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
"spotlightSearch",
|
||||||
): Chainable<JQuery<HTMLElement>> => {
|
(options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<HTMLElement>> => {
|
||||||
return cy.get(".mx_SpotlightDialog_searchBox input", options);
|
return cy.get(".mx_SpotlightDialog_searchBox input", options);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("spotlightResults", (
|
Cypress.Commands.add(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
"spotlightResults",
|
||||||
): Chainable<JQuery<HTMLElement>> => {
|
(options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<HTMLElement>> => {
|
||||||
return cy.get(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option", options);
|
return cy.get(".mx_SpotlightDialog_section.mx_SpotlightDialog_results .mx_SpotlightDialog_option", options);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("roomHeaderName", (
|
Cypress.Commands.add(
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
"roomHeaderName",
|
||||||
): Chainable<JQuery<HTMLElement>> => {
|
(options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<JQuery<HTMLElement>> => {
|
||||||
return cy.get(".mx_RoomHeader_nametext", options);
|
return cy.get(".mx_RoomHeader_nametext", options);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("startDM", (name: string) => {
|
Cypress.Commands.add("startDM", (name: string) => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog().within(() => {
|
||||||
|
@ -121,9 +129,7 @@ Cypress.Commands.add("startDM", (name: string) => {
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).click();
|
||||||
});
|
});
|
||||||
// send first message to start DM
|
// send first message to start DM
|
||||||
cy.get(".mx_BasicMessageComposer_input")
|
cy.get(".mx_BasicMessageComposer_input").should("have.focus").type("Hey!{enter}");
|
||||||
.should("have.focus")
|
|
||||||
.type("Hey!{enter}");
|
|
||||||
// The DM room is created at this point, this can take a little bit of time
|
// The DM room is created at this point, this can take a little bit of time
|
||||||
cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 });
|
cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 });
|
||||||
cy.contains(".mx_RoomSublist[aria-label=People]", name);
|
cy.contains(".mx_RoomSublist[aria-label=People]", name);
|
||||||
|
@ -148,46 +154,52 @@ describe("Spotlight", () => {
|
||||||
let room3Id: string;
|
let room3Id: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, "Jim").then(() =>
|
cy.initTestUser(synapse, "Jim")
|
||||||
cy.getBot(synapse, { displayName: bot1Name }).then(_bot1 => {
|
.then(() =>
|
||||||
|
cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => {
|
||||||
bot1 = _bot1;
|
bot1 = _bot1;
|
||||||
}),
|
}),
|
||||||
).then(() =>
|
)
|
||||||
cy.getBot(synapse, { displayName: bot2Name }).then(_bot2 => {
|
.then(() =>
|
||||||
|
cy.getBot(synapse, { displayName: bot2Name }).then((_bot2) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
bot2 = _bot2;
|
bot2 = _bot2;
|
||||||
}),
|
}),
|
||||||
).then(() =>
|
)
|
||||||
|
.then(() =>
|
||||||
cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => {
|
cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => {
|
||||||
cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then(_room1Id => {
|
cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then((_room1Id) => {
|
||||||
room1Id = _room1Id;
|
room1Id = _room1Id;
|
||||||
bot1.joinRoom(room1Id);
|
bot1.joinRoom(room1Id);
|
||||||
cy.visit("/#/room/" + room1Id);
|
cy.visit("/#/room/" + room1Id);
|
||||||
});
|
});
|
||||||
bot2.createRoom({ name: room2Name, visibility: Visibility.Public })
|
bot2.createRoom({ name: room2Name, visibility: Visibility.Public }).then(
|
||||||
.then(({ room_id: _room2Id }) => {
|
({ room_id: _room2Id }) => {
|
||||||
room2Id = _room2Id;
|
room2Id = _room2Id;
|
||||||
bot2.invite(room2Id, bot1.getUserId());
|
bot2.invite(room2Id, bot1.getUserId());
|
||||||
});
|
},
|
||||||
|
);
|
||||||
bot2.createRoom({
|
bot2.createRoom({
|
||||||
name: room3Name,
|
name: room3Name,
|
||||||
visibility: Visibility.Public, initial_state: [{
|
visibility: Visibility.Public,
|
||||||
|
initial_state: [
|
||||||
|
{
|
||||||
type: "m.room.history_visibility",
|
type: "m.room.history_visibility",
|
||||||
state_key: "",
|
state_key: "",
|
||||||
content: {
|
content: {
|
||||||
history_visibility: "world_readable",
|
history_visibility: "world_readable",
|
||||||
},
|
},
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
}).then(({ room_id: _room3Id }) => {
|
}).then(({ room_id: _room3Id }) => {
|
||||||
room3Id = _room3Id;
|
room3Id = _room3Id;
|
||||||
bot2.invite(room3Id, bot1.getUserId());
|
bot2.invite(room3Id, bot1.getUserId());
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
).then(() =>
|
)
|
||||||
cy.get('.mx_RoomSublist_skeletonUI').should('not.exist'),
|
.then(() => cy.get(".mx_RoomSublist_skeletonUI").should("not.exist"));
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -216,20 +228,23 @@ describe("Spotlight", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find joined rooms", () => {
|
it("should find joined rooms", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
|
.within(() => {
|
||||||
cy.spotlightSearch().clear().type(room1Name);
|
cy.spotlightSearch().clear().type(room1Name);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).should("contain", room1Name);
|
cy.spotlightResults().eq(0).should("contain", room1Name);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).click();
|
||||||
cy.url().should("contain", room1Id);
|
cy.url().should("contain", room1Id);
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.roomHeaderName().should("contain", room1Name);
|
cy.roomHeaderName().should("contain", room1Name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find known public rooms", () => {
|
it("should find known public rooms", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
|
.within(() => {
|
||||||
cy.spotlightFilter(Filter.PublicRooms);
|
cy.spotlightFilter(Filter.PublicRooms);
|
||||||
cy.spotlightSearch().clear().type(room1Name);
|
cy.spotlightSearch().clear().type(room1Name);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
|
@ -238,13 +253,15 @@ describe("Spotlight", () => {
|
||||||
cy.spotlightResults().eq(0).should("contain", "View");
|
cy.spotlightResults().eq(0).should("contain", "View");
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).click();
|
||||||
cy.url().should("contain", room1Id);
|
cy.url().should("contain", room1Id);
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.roomHeaderName().should("contain", room1Name);
|
cy.roomHeaderName().should("contain", room1Name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find unknown public rooms", () => {
|
it("should find unknown public rooms", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
|
.within(() => {
|
||||||
cy.spotlightFilter(Filter.PublicRooms);
|
cy.spotlightFilter(Filter.PublicRooms);
|
||||||
cy.spotlightSearch().clear().type(room2Name);
|
cy.spotlightSearch().clear().type(room2Name);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
|
@ -253,14 +270,16 @@ describe("Spotlight", () => {
|
||||||
cy.spotlightResults().eq(0).should("contain", "Join");
|
cy.spotlightResults().eq(0).should("contain", "Join");
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).click();
|
||||||
cy.url().should("contain", room2Id);
|
cy.url().should("contain", room2Id);
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.get(".mx_RoomView_MessageList").should("have.length", 1);
|
cy.get(".mx_RoomView_MessageList").should("have.length", 1);
|
||||||
cy.roomHeaderName().should("contain", room2Name);
|
cy.roomHeaderName().should("contain", room2Name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find unknown public world readable rooms", () => {
|
it("should find unknown public world readable rooms", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
|
.within(() => {
|
||||||
cy.spotlightFilter(Filter.PublicRooms);
|
cy.spotlightFilter(Filter.PublicRooms);
|
||||||
cy.spotlightSearch().clear().type(room3Name);
|
cy.spotlightSearch().clear().type(room3Name);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
|
@ -269,7 +288,8 @@ describe("Spotlight", () => {
|
||||||
cy.spotlightResults().eq(0).should("contain", "View");
|
cy.spotlightResults().eq(0).should("contain", "View");
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).click();
|
||||||
cy.url().should("contain", room3Id);
|
cy.url().should("contain", room3Id);
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.get(".mx_RoomPreviewBar_actions .mx_AccessibleButton").click();
|
cy.get(".mx_RoomPreviewBar_actions .mx_AccessibleButton").click();
|
||||||
cy.roomHeaderName().should("contain", room3Name);
|
cy.roomHeaderName().should("contain", room3Name);
|
||||||
});
|
});
|
||||||
|
@ -299,27 +319,31 @@ describe("Spotlight", () => {
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
it("should find known people", () => {
|
it("should find known people", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
|
.within(() => {
|
||||||
cy.spotlightFilter(Filter.People);
|
cy.spotlightFilter(Filter.People);
|
||||||
cy.spotlightSearch().clear().type(bot1Name);
|
cy.spotlightSearch().clear().type(bot1Name);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).should("contain", bot1Name);
|
cy.spotlightResults().eq(0).should("contain", bot1Name);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).click();
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.roomHeaderName().should("contain", bot1Name);
|
cy.roomHeaderName().should("contain", bot1Name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find unknown people", () => {
|
it("should find unknown people", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
|
.within(() => {
|
||||||
cy.spotlightFilter(Filter.People);
|
cy.spotlightFilter(Filter.People);
|
||||||
cy.spotlightSearch().clear().type(bot2Name);
|
cy.spotlightSearch().clear().type(bot2Name);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).click();
|
||||||
}).then(() => {
|
})
|
||||||
|
.then(() => {
|
||||||
cy.roomHeaderName().should("contain", bot2Name);
|
cy.roomHeaderName().should("contain", bot2Name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -340,10 +364,7 @@ describe("Spotlight", () => {
|
||||||
|
|
||||||
// Send first message to actually start DM
|
// Send first message to actually start DM
|
||||||
cy.roomHeaderName().should("contain", bot2Name);
|
cy.roomHeaderName().should("contain", bot2Name);
|
||||||
cy.get(".mx_BasicMessageComposer_input")
|
cy.get(".mx_BasicMessageComposer_input").click().should("have.focus").type("Hey!{enter}");
|
||||||
.click()
|
|
||||||
.should("have.focus")
|
|
||||||
.type("Hey!{enter}");
|
|
||||||
|
|
||||||
// Assert DM exists by checking for the first message and the room being in the room list
|
// Assert DM exists by checking for the first message and the room being in the room list
|
||||||
cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 });
|
cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 });
|
||||||
|
@ -352,13 +373,13 @@ describe("Spotlight", () => {
|
||||||
// Invite BotBob into existing DM with ByteBot
|
// Invite BotBob into existing DM with ByteBot
|
||||||
cy.getDmRooms(bot2.getUserId())
|
cy.getDmRooms(bot2.getUserId())
|
||||||
.should("have.length", 1)
|
.should("have.length", 1)
|
||||||
.then(dmRooms => cy.getClient().then(client => client.getRoom(dmRooms[0])))
|
.then((dmRooms) => cy.getClient().then((client) => client.getRoom(dmRooms[0])))
|
||||||
.then(groupDm => {
|
.then((groupDm) => {
|
||||||
cy.inviteUser(groupDm.roomId, bot1.getUserId());
|
cy.inviteUser(groupDm.roomId, bot1.getUserId());
|
||||||
cy.roomHeaderName().should(($element) =>
|
cy.roomHeaderName().should(($element) => expect($element.get(0).innerText).contains(groupDm.name));
|
||||||
expect($element.get(0).innerText).contains(groupDm.name));
|
|
||||||
cy.get(".mx_RoomSublist[aria-label=People]").should(($element) =>
|
cy.get(".mx_RoomSublist[aria-label=People]").should(($element) =>
|
||||||
expect($element.get(0).innerText).contains(groupDm.name));
|
expect($element.get(0).innerText).contains(groupDm.name),
|
||||||
|
);
|
||||||
|
|
||||||
// Search for BotBob by id, should return group DM and user
|
// Search for BotBob by id, should return group DM and user
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog().within(() => {
|
||||||
|
@ -407,7 +428,8 @@ describe("Spotlight", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow opening group chat dialog", () => {
|
it("should allow opening group chat dialog", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
|
.within(() => {
|
||||||
cy.spotlightFilter(Filter.People);
|
cy.spotlightFilter(Filter.People);
|
||||||
cy.spotlightSearch().clear().type(bot2Name);
|
cy.spotlightSearch().clear().type(bot2Name);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
|
@ -415,8 +437,9 @@ describe("Spotlight", () => {
|
||||||
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
||||||
cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat");
|
cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat");
|
||||||
cy.get(".mx_SpotlightDialog_startGroupChat").click();
|
cy.get(".mx_SpotlightDialog_startGroupChat").click();
|
||||||
}).then(() => {
|
})
|
||||||
cy.get('[role=dialog]').should("contain", "Direct Messages");
|
.then(() => {
|
||||||
|
cy.get("[role=dialog]").should("contain", "Direct Messages");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -445,36 +468,38 @@ describe("Spotlight", () => {
|
||||||
// our debouncing logic only starts the search after a short timeout,
|
// our debouncing logic only starts the search after a short timeout,
|
||||||
// so we wait a few milliseconds.
|
// so we wait a few milliseconds.
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
cy.get(".mx_Spinner").should("not.exist").then(() => {
|
cy.get(".mx_Spinner")
|
||||||
cy.spotlightResults().should("have.length", 2).then(() => {
|
.should("not.exist")
|
||||||
cy.spotlightResults().eq(0)
|
.then(() => {
|
||||||
.should("have.attr", "aria-selected", "true");
|
cy.spotlightResults()
|
||||||
cy.spotlightResults().eq(1)
|
.should("have.length", 2)
|
||||||
.should("have.attr", "aria-selected", "false");
|
.then(() => {
|
||||||
|
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "true");
|
||||||
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false");
|
||||||
});
|
});
|
||||||
cy.spotlightSearch().type("{downArrow}").then(() => {
|
cy.spotlightSearch()
|
||||||
cy.spotlightResults().eq(0)
|
.type("{downArrow}")
|
||||||
.should("have.attr", "aria-selected", "false");
|
.then(() => {
|
||||||
cy.spotlightResults().eq(1)
|
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
|
||||||
.should("have.attr", "aria-selected", "true");
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true");
|
||||||
});
|
});
|
||||||
cy.spotlightSearch().type("{downArrow}").then(() => {
|
cy.spotlightSearch()
|
||||||
cy.spotlightResults().eq(0)
|
.type("{downArrow}")
|
||||||
.should("have.attr", "aria-selected", "false");
|
.then(() => {
|
||||||
cy.spotlightResults().eq(1)
|
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
|
||||||
.should("have.attr", "aria-selected", "false");
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false");
|
||||||
});
|
});
|
||||||
cy.spotlightSearch().type("{upArrow}").then(() => {
|
cy.spotlightSearch()
|
||||||
cy.spotlightResults().eq(0)
|
.type("{upArrow}")
|
||||||
.should("have.attr", "aria-selected", "false");
|
.then(() => {
|
||||||
cy.spotlightResults().eq(1)
|
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
|
||||||
.should("have.attr", "aria-selected", "true");
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true");
|
||||||
});
|
});
|
||||||
cy.spotlightSearch().type("{upArrow}").then(() => {
|
cy.spotlightSearch()
|
||||||
cy.spotlightResults().eq(0)
|
.type("{upArrow}")
|
||||||
.should("have.attr", "aria-selected", "true");
|
.then(() => {
|
||||||
cy.spotlightResults().eq(1)
|
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "true");
|
||||||
.should("have.attr", "aria-selected", "false");
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { MatrixClient } from "../../global";
|
||||||
|
|
||||||
function markWindowBeforeReload(): void {
|
function markWindowBeforeReload(): void {
|
||||||
// mark our window object to "know" when it gets reloaded
|
// mark our window object to "know" when it gets reloaded
|
||||||
cy.window().then(w => w.beforeReload = true);
|
cy.window().then((w) => (w.beforeReload = true));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Threads", () => {
|
describe("Threads", () => {
|
||||||
|
@ -30,10 +30,10 @@ describe("Threads", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Default threads to ON for this spec
|
// Default threads to ON for this spec
|
||||||
cy.enableLabsFeature("feature_thread");
|
cy.enableLabsFeature("feature_thread");
|
||||||
cy.window().then(win => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
|
||||||
});
|
});
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Tom");
|
cy.initTestUser(synapse, "Tom");
|
||||||
|
@ -78,12 +78,12 @@ describe("Threads", () => {
|
||||||
cy.getBot(synapse, {
|
cy.getBot(synapse, {
|
||||||
displayName: "BotBob",
|
displayName: "BotBob",
|
||||||
autoAcceptInvites: false,
|
autoAcceptInvites: false,
|
||||||
}).then(_bot => {
|
}).then((_bot) => {
|
||||||
bot = _bot;
|
bot = _bot;
|
||||||
});
|
});
|
||||||
|
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.inviteUser(roomId, bot.getUserId());
|
cy.inviteUser(roomId, bot.getUserId());
|
||||||
bot.joinRoom(roomId);
|
bot.joinRoom(roomId);
|
||||||
|
@ -95,10 +95,11 @@ describe("Threads", () => {
|
||||||
|
|
||||||
// Wait for message to send, get its ID and save as @threadId
|
// Wait for message to send, get its ID and save as @threadId
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||||
.invoke("attr", "data-scroll-tokens").as("threadId");
|
.invoke("attr", "data-scroll-tokens")
|
||||||
|
.as("threadId");
|
||||||
|
|
||||||
// Bot starts thread
|
// Bot starts thread
|
||||||
cy.get<string>("@threadId").then(threadId => {
|
cy.get<string>("@threadId").then((threadId) => {
|
||||||
bot.sendMessage(roomId, threadId, {
|
bot.sendMessage(roomId, threadId, {
|
||||||
body: "Hello there",
|
body: "Hello there",
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
|
@ -119,7 +120,8 @@ describe("Threads", () => {
|
||||||
|
|
||||||
// User reacts to message instead
|
// User reacts to message instead
|
||||||
cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Hello there")
|
cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Hello there")
|
||||||
.find('[aria-label="React"]').click({ force: true }); // Cypress has no ability to hover
|
.find('[aria-label="React"]')
|
||||||
|
.click({ force: true }); // Cypress has no ability to hover
|
||||||
cy.get(".mx_EmojiPicker").within(() => {
|
cy.get(".mx_EmojiPicker").within(() => {
|
||||||
cy.get('input[type="text"]').type("wave");
|
cy.get('input[type="text"]').type("wave");
|
||||||
cy.contains('[role="menuitem"]', "👋").click();
|
cy.contains('[role="menuitem"]', "👋").click();
|
||||||
|
@ -127,7 +129,8 @@ describe("Threads", () => {
|
||||||
|
|
||||||
// User redacts their prior response
|
// User redacts their prior response
|
||||||
cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Test")
|
cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Test")
|
||||||
.find('[aria-label="Options"]').click({ force: true }); // Cypress has no ability to hover
|
.find('[aria-label="Options"]')
|
||||||
|
.click({ force: true }); // Cypress has no ability to hover
|
||||||
cy.get(".mx_IconizedContextMenu").within(() => {
|
cy.get(".mx_IconizedContextMenu").within(() => {
|
||||||
cy.contains('[role="menuitem"]', "Remove").click();
|
cy.contains('[role="menuitem"]', "Remove").click();
|
||||||
});
|
});
|
||||||
|
@ -144,7 +147,7 @@ describe("Threads", () => {
|
||||||
cy.get(".mx_ThreadPanel .mx_BaseCard_close").click();
|
cy.get(".mx_ThreadPanel .mx_BaseCard_close").click();
|
||||||
|
|
||||||
// Bot responds to thread
|
// Bot responds to thread
|
||||||
cy.get<string>("@threadId").then(threadId => {
|
cy.get<string>("@threadId").then((threadId) => {
|
||||||
bot.sendMessage(roomId, threadId, {
|
bot.sendMessage(roomId, threadId, {
|
||||||
body: "How are things?",
|
body: "How are things?",
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
|
@ -178,45 +181,55 @@ describe("Threads", () => {
|
||||||
cy.get(".mx_BasicMessageComposer_input").type(" How about yourself?{enter}");
|
cy.get(".mx_BasicMessageComposer_input").type(" How about yourself?{enter}");
|
||||||
});
|
});
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom");
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom");
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content")
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should(
|
||||||
.should("contain", "Great! How about yourself?");
|
"contain",
|
||||||
|
"Great! How about yourself?",
|
||||||
|
);
|
||||||
|
|
||||||
// User closes right panel
|
// User closes right panel
|
||||||
cy.get(".mx_ThreadView .mx_BaseCard_close").click();
|
cy.get(".mx_ThreadView .mx_BaseCard_close").click();
|
||||||
|
|
||||||
// Bot responds to thread and saves the id of their message to @eventId
|
// Bot responds to thread and saves the id of their message to @eventId
|
||||||
cy.get<string>("@threadId").then(threadId => {
|
cy.get<string>("@threadId").then((threadId) => {
|
||||||
cy.wrap(bot.sendMessage(roomId, threadId, {
|
cy.wrap(
|
||||||
|
bot
|
||||||
|
.sendMessage(roomId, threadId, {
|
||||||
body: "I'm very good thanks",
|
body: "I'm very good thanks",
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
}).then(res => res.event_id)).as("eventId");
|
})
|
||||||
|
.then((res) => res.event_id),
|
||||||
|
).as("eventId");
|
||||||
});
|
});
|
||||||
|
|
||||||
// User asserts
|
// User asserts
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content")
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should(
|
||||||
.should("contain", "I'm very good thanks");
|
"contain",
|
||||||
|
"I'm very good thanks",
|
||||||
|
);
|
||||||
|
|
||||||
// Bot edits their latest event
|
// Bot edits their latest event
|
||||||
cy.get<string>("@eventId").then(eventId => {
|
cy.get<string>("@eventId").then((eventId) => {
|
||||||
bot.sendMessage(roomId, {
|
bot.sendMessage(roomId, {
|
||||||
"body": "* I'm very good thanks :)",
|
"body": "* I'm very good thanks :)",
|
||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
"m.new_content": {
|
"m.new_content": {
|
||||||
"body": "I'm very good thanks :)",
|
body: "I'm very good thanks :)",
|
||||||
"msgtype": "m.text",
|
msgtype: "m.text",
|
||||||
},
|
},
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
"rel_type": "m.replace",
|
rel_type: "m.replace",
|
||||||
"event_id": eventId,
|
event_id: eventId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// User asserts
|
// User asserts
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content")
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should(
|
||||||
.should("contain", "I'm very good thanks :)");
|
"contain",
|
||||||
|
"I'm very good thanks :)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can send voice messages", () => {
|
it("can send voice messages", () => {
|
||||||
|
@ -227,7 +240,7 @@ describe("Threads", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
@ -237,7 +250,9 @@ describe("Threads", () => {
|
||||||
|
|
||||||
// Create thread
|
// Create thread
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
.realHover()
|
||||||
|
.find(".mx_MessageActionBar_threadButton")
|
||||||
|
.click();
|
||||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||||
|
|
||||||
cy.openMessageComposerOptions(true).find(`[aria-label="Voice Message"]`).click();
|
cy.openMessageComposerOptions(true).find(`[aria-label="Voice Message"]`).click();
|
||||||
|
@ -250,7 +265,7 @@ describe("Threads", () => {
|
||||||
it("right panel behaves correctly", () => {
|
it("right panel behaves correctly", () => {
|
||||||
// Create room
|
// Create room
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then((_roomId) => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
@ -259,7 +274,9 @@ describe("Threads", () => {
|
||||||
|
|
||||||
// Create thread
|
// Create thread
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
.realHover()
|
||||||
|
.find(".mx_MessageActionBar_threadButton")
|
||||||
|
.click();
|
||||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||||
|
|
||||||
// Send message to thread
|
// Send message to thread
|
||||||
|
@ -271,7 +288,9 @@ describe("Threads", () => {
|
||||||
|
|
||||||
// Open existing thread
|
// Open existing thread
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
.realHover()
|
||||||
|
.find(".mx_MessageActionBar_threadButton")
|
||||||
|
.click();
|
||||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||||
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. Bot");
|
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. Bot");
|
||||||
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. User");
|
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. User");
|
||||||
|
|
|
@ -45,10 +45,7 @@ const expectDisplayName = (e: JQuery<HTMLElement>, displayName: string): void =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectAvatar = (e: JQuery<HTMLElement>, avatarUrl: string): void => {
|
const expectAvatar = (e: JQuery<HTMLElement>, avatarUrl: string): void => {
|
||||||
cy.all([
|
cy.all([cy.window({ log: false }), cy.getClient()]).then(([win, cli]) => {
|
||||||
cy.window({ log: false }),
|
|
||||||
cy.getClient(),
|
|
||||||
]).then(([win, cli]) => {
|
|
||||||
const size = AVATAR_SIZE * win.devicePixelRatio;
|
const size = AVATAR_SIZE * win.devicePixelRatio;
|
||||||
expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal(
|
expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal(
|
||||||
// eslint-disable-next-line no-restricted-properties
|
// eslint-disable-next-line no-restricted-properties
|
||||||
|
@ -75,10 +72,10 @@ describe("Timeline", () => {
|
||||||
let newAvatarUrl: string;
|
let newAvatarUrl: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, OLD_NAME).then(() =>
|
cy.initTestUser(synapse, OLD_NAME).then(() =>
|
||||||
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
|
cy.createRoom({ name: ROOM_NAME }).then((_room1Id) => {
|
||||||
roomId = _room1Id;
|
roomId = _room1Id;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -154,8 +151,11 @@ describe("Timeline", () => {
|
||||||
it("should create and configure a room on IRC layout", () => {
|
it("should create and configure a room on IRC layout", () => {
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
cy.contains(".mx_RoomView_body .mx_GenericEventListSummary[data-layout=irc] " +
|
cy.contains(
|
||||||
".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist");
|
".mx_RoomView_body .mx_GenericEventListSummary[data-layout=irc] " +
|
||||||
|
".mx_GenericEventListSummary_summary",
|
||||||
|
"created and configured the room.",
|
||||||
|
).should("exist");
|
||||||
cy.get(".mx_Spinner").should("not.exist");
|
cy.get(".mx_Spinner").should("not.exist");
|
||||||
cy.percySnapshot("Configured room on IRC layout");
|
cy.percySnapshot("Configured room on IRC layout");
|
||||||
});
|
});
|
||||||
|
@ -165,8 +165,10 @@ describe("Timeline", () => {
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
|
|
||||||
// Wait until configuration is finished
|
// Wait until configuration is finished
|
||||||
cy.contains(".mx_RoomView_body .mx_GenericEventListSummary " +
|
cy.contains(
|
||||||
".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist");
|
".mx_RoomView_body .mx_GenericEventListSummary " + ".mx_GenericEventListSummary_summary",
|
||||||
|
"created and configured the room.",
|
||||||
|
).should("exist");
|
||||||
|
|
||||||
// Click "expand" link button
|
// Click "expand" link button
|
||||||
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click();
|
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click();
|
||||||
|
@ -177,13 +179,13 @@ describe("Timeline", () => {
|
||||||
// = calc(var(--name-width) + 10px + var(--icon-width))
|
// = calc(var(--name-width) + 10px + var(--icon-width))
|
||||||
// = 80 + 10 + 14 = 104px
|
// = 80 + 10 + 14 = 104px
|
||||||
cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line")
|
cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line")
|
||||||
.should('have.css', "margin-inline-start", "104px")
|
.should("have.css", "margin-inline-start", "104px")
|
||||||
.should('have.css', "inset-inline-start", "0px");
|
.should("have.css", "inset-inline-start", "0px");
|
||||||
|
|
||||||
cy.get(".mx_Spinner").should("not.exist");
|
cy.get(".mx_Spinner").should("not.exist");
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp from snapshot
|
||||||
const percyCSS = ".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp "
|
const percyCSS =
|
||||||
+ "{ visibility: hidden !important; }";
|
".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp " + "{ visibility: hidden !important; }";
|
||||||
cy.percySnapshot("Event line with inline start margin on IRC layout", { percyCSS });
|
cy.percySnapshot("Event line with inline start margin on IRC layout", { percyCSS });
|
||||||
cy.checkA11y();
|
cy.checkA11y();
|
||||||
});
|
});
|
||||||
|
@ -192,8 +194,10 @@ describe("Timeline", () => {
|
||||||
sendEvent(roomId);
|
sendEvent(roomId);
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
||||||
cy.contains(".mx_RoomView_body .mx_GenericEventListSummary .mx_GenericEventListSummary_summary",
|
cy.contains(
|
||||||
"created and configured the room.").should("exist");
|
".mx_RoomView_body .mx_GenericEventListSummary .mx_GenericEventListSummary_summary",
|
||||||
|
"created and configured the room.",
|
||||||
|
).should("exist");
|
||||||
|
|
||||||
// Edit message
|
// Edit message
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile .mx_EventTile_line", "Message").within(() => {
|
cy.contains(".mx_RoomView_body .mx_EventTile .mx_EventTile_line", "Message").within(() => {
|
||||||
|
@ -206,20 +210,23 @@ describe("Timeline", () => {
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp").click();
|
cy.get(".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp").click();
|
||||||
|
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp from snapshot
|
||||||
const percyCSS = ".mx_RoomView_body .mx_EventTile .mx_MessageTimestamp "
|
const percyCSS =
|
||||||
+ "{ visibility: hidden !important; }";
|
".mx_RoomView_body .mx_EventTile .mx_MessageTimestamp " + "{ visibility: hidden !important; }";
|
||||||
|
|
||||||
// should not add inline start padding to a hidden event line on IRC layout
|
// should not add inline start padding to a hidden event line on IRC layout
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info .mx_EventTile_line")
|
cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info .mx_EventTile_line").should(
|
||||||
.should('have.css', 'padding-inline-start', '0px');
|
"have.css",
|
||||||
|
"padding-inline-start",
|
||||||
|
"0px",
|
||||||
|
);
|
||||||
cy.percySnapshot("Hidden event line with zero padding on IRC layout", { percyCSS });
|
cy.percySnapshot("Hidden event line with zero padding on IRC layout", { percyCSS });
|
||||||
|
|
||||||
// should add inline start padding to a hidden event line on modern layout
|
// should add inline start padding to a hidden event line on modern layout
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||||
cy.get(".mx_EventTile[data-layout=group].mx_EventTile_info .mx_EventTile_line")
|
cy.get(".mx_EventTile[data-layout=group].mx_EventTile_info .mx_EventTile_line")
|
||||||
// calc(var(--EventTile_group_line-spacing-inline-start) + 20px) = 64 + 20 = 84px
|
// calc(var(--EventTile_group_line-spacing-inline-start) + 20px) = 64 + 20 = 84px
|
||||||
.should('have.css', 'padding-inline-start', '84px');
|
.should("have.css", "padding-inline-start", "84px");
|
||||||
cy.percySnapshot("Hidden event line with padding on modern layout", { percyCSS });
|
cy.percySnapshot("Hidden event line with padding on modern layout", { percyCSS });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -227,8 +234,10 @@ describe("Timeline", () => {
|
||||||
sendEvent(roomId);
|
sendEvent(roomId);
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
||||||
cy.contains(".mx_RoomView_body .mx_GenericEventListSummary " +
|
cy.contains(
|
||||||
".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist");
|
".mx_RoomView_body .mx_GenericEventListSummary " + ".mx_GenericEventListSummary_summary",
|
||||||
|
"created and configured the room.",
|
||||||
|
).should("exist");
|
||||||
|
|
||||||
// Edit message
|
// Edit message
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile .mx_EventTile_line", "Message").within(() => {
|
cy.contains(".mx_RoomView_body .mx_EventTile .mx_EventTile_line", "Message").within(() => {
|
||||||
|
@ -238,8 +247,11 @@ describe("Timeline", () => {
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "MessageEdit").should("exist");
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "MessageEdit").should("exist");
|
||||||
|
|
||||||
// Click top left of the event toggle, which should not be covered by MessageActionBar's safe area
|
// Click top left of the event toggle, which should not be covered by MessageActionBar's safe area
|
||||||
cy.get(".mx_EventTile .mx_ViewSourceEvent").should("exist").realHover().within(() => {
|
cy.get(".mx_EventTile .mx_ViewSourceEvent")
|
||||||
cy.get(".mx_ViewSourceEvent_toggle").click('topLeft', { force: false });
|
.should("exist")
|
||||||
|
.realHover()
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".mx_ViewSourceEvent_toggle").click("topLeft", { force: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure the expand toggle worked
|
// Make sure the expand toggle worked
|
||||||
|
@ -249,8 +261,11 @@ describe("Timeline", () => {
|
||||||
it("should click 'collapse' link button on the first hovered info event line on bubble layout", () => {
|
it("should click 'collapse' link button on the first hovered info event line on bubble layout", () => {
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
||||||
cy.contains(".mx_RoomView_body .mx_GenericEventListSummary[data-layout=bubble] " +
|
cy.contains(
|
||||||
".mx_GenericEventListSummary_summary", "created and configured the room.").should("exist");
|
".mx_RoomView_body .mx_GenericEventListSummary[data-layout=bubble] " +
|
||||||
|
".mx_GenericEventListSummary_summary",
|
||||||
|
"created and configured the room.",
|
||||||
|
).should("exist");
|
||||||
|
|
||||||
// Click "expand" link button
|
// Click "expand" link button
|
||||||
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click();
|
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click();
|
||||||
|
@ -340,10 +355,14 @@ describe("Timeline", () => {
|
||||||
|
|
||||||
cy.getComposer().type(`${reply}{enter}`);
|
cy.getComposer().type(`${reply}{enter}`);
|
||||||
|
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody")
|
cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody").should(
|
||||||
.should("contain", MESSAGE);
|
"contain",
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MTextBody", reply)
|
MESSAGE,
|
||||||
.should("have.length", 1);
|
);
|
||||||
|
cy.contains(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MTextBody", reply).should(
|
||||||
|
"have.length",
|
||||||
|
1,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can reply with a voice message", () => {
|
it("can reply with a voice message", () => {
|
||||||
|
@ -355,10 +374,14 @@ describe("Timeline", () => {
|
||||||
cy.wait(3000);
|
cy.wait(3000);
|
||||||
cy.get(".mx_RoomView_body .mx_MessageComposer .mx_MessageComposer_sendMessage").click();
|
cy.get(".mx_RoomView_body .mx_MessageComposer .mx_MessageComposer_sendMessage").click();
|
||||||
|
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody")
|
cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_ReplyTile .mx_MTextBody").should(
|
||||||
.should("contain", MESSAGE);
|
"contain",
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MVoiceMessageBody")
|
MESSAGE,
|
||||||
.should("have.length", 1);
|
);
|
||||||
|
cy.get(".mx_RoomView_body .mx_EventTile > .mx_EventTile_line > .mx_MVoiceMessageBody").should(
|
||||||
|
"have.length",
|
||||||
|
1,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,15 +47,15 @@ describe("Analytics Toast", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show an analytics toast if config has nothing about posthog", () => {
|
it("should not show an analytics toast if config has nothing about posthog", () => {
|
||||||
cy.intercept("/config.json?cachebuster=*", req => {
|
cy.intercept("/config.json?cachebuster=*", (req) => {
|
||||||
req.continue(res => {
|
req.continue((res) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { posthog, ...body } = res.body;
|
const { posthog, ...body } = res.body;
|
||||||
res.send(200, body);
|
res.send(200, body);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, "Tod");
|
cy.initTestUser(synapse, "Tod");
|
||||||
});
|
});
|
||||||
|
@ -66,8 +66,8 @@ describe("Analytics Toast", () => {
|
||||||
|
|
||||||
describe("with posthog enabled", () => {
|
describe("with posthog enabled", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept("/config.json?cachebuster=*", req => {
|
cy.intercept("/config.json?cachebuster=*", (req) => {
|
||||||
req.continue(res => {
|
req.continue((res) => {
|
||||||
res.send(200, {
|
res.send(200, {
|
||||||
...res.body,
|
...res.body,
|
||||||
posthog: {
|
posthog: {
|
||||||
|
@ -78,7 +78,7 @@ describe("Analytics Toast", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, "Tod");
|
cy.initTestUser(synapse, "Tod");
|
||||||
rejectToast("Notifications");
|
rejectToast("Notifications");
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe("Update", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -45,7 +45,9 @@ describe("Update", () => {
|
||||||
cy.initTestUser(synapse, "Ursa");
|
cy.initTestUser(synapse, "Ursa");
|
||||||
|
|
||||||
cy.wait("@version");
|
cy.wait("@version");
|
||||||
cy.url().should("contain", "updated=" + NEW_VERSION).then(href => {
|
cy.url()
|
||||||
|
.should("contain", "updated=" + NEW_VERSION)
|
||||||
|
.then((href) => {
|
||||||
const url = new URL(href);
|
const url = new URL(href);
|
||||||
expect(url.searchParams.get("updated")).to.equal(NEW_VERSION);
|
expect(url.searchParams.get("updated")).to.equal(NEW_VERSION);
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,10 +24,10 @@ describe("User Menu", () => {
|
||||||
let user: UserCredentials;
|
let user: UserCredentials;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Jeff").then(credentials => {
|
cy.initTestUser(synapse, "Jeff").then((credentials) => {
|
||||||
user = credentials;
|
user = credentials;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,23 +26,23 @@ describe("User Onboarding (new user)", () => {
|
||||||
let bot1: MatrixClient;
|
let bot1: MatrixClient;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, "Jane Doe");
|
cy.initTestUser(synapse, "Jane Doe");
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
win.localStorage.setItem("mx_registration_time", "1656633601");
|
win.localStorage.setItem("mx_registration_time", "1656633601");
|
||||||
});
|
});
|
||||||
cy.reload().then(() => {
|
cy.reload().then(() => {
|
||||||
// wait for the app to load
|
// wait for the app to load
|
||||||
return cy.get(".mx_MatrixChat", { timeout: 15000 });
|
return cy.get(".mx_MatrixChat", { timeout: 15000 });
|
||||||
});
|
});
|
||||||
cy.getBot(synapse, { displayName: bot1Name }).then(_bot1 => {
|
cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => {
|
||||||
bot1 = _bot1;
|
bot1 = _bot1;
|
||||||
});
|
});
|
||||||
cy.get('.mx_UserOnboardingPage').should('exist');
|
cy.get(".mx_UserOnboardingPage").should("exist");
|
||||||
cy.get('.mx_UserOnboardingButton').should('exist');
|
cy.get(".mx_UserOnboardingButton").should("exist");
|
||||||
cy.get('.mx_UserOnboardingList')
|
cy.get(".mx_UserOnboardingList")
|
||||||
.should('exist')
|
.should("exist")
|
||||||
.should(($list) => {
|
.should(($list) => {
|
||||||
const list = $list.get(0);
|
const list = $list.get(0);
|
||||||
expect(getComputedStyle(list).opacity).to.be.eq("1");
|
expect(getComputedStyle(list).opacity).to.be.eq("1");
|
||||||
|
@ -55,25 +55,23 @@ describe("User Onboarding (new user)", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("page is shown and preference exists", () => {
|
it("page is shown and preference exists", () => {
|
||||||
cy.get('.mx_UserOnboardingPage')
|
cy.get(".mx_UserOnboardingPage").percySnapshotElement("User onboarding page");
|
||||||
.percySnapshotElement("User onboarding page");
|
|
||||||
cy.openUserSettings("Preferences");
|
cy.openUserSettings("Preferences");
|
||||||
cy.contains("Show shortcut to welcome checklist above the room list").should("exist");
|
cy.contains("Show shortcut to welcome checklist above the room list").should("exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("app download dialog", () => {
|
it("app download dialog", () => {
|
||||||
cy.contains(".mx_UserOnboardingTask_action", "Download apps").click();
|
cy.contains(".mx_UserOnboardingTask_action", "Download apps").click();
|
||||||
cy.get('[role=dialog]')
|
cy.get("[role=dialog]").contains("#mx_BaseDialog_title", "Download Element").should("exist");
|
||||||
.contains("#mx_BaseDialog_title", "Download Element")
|
cy.get("[role=dialog]").percySnapshotElement("App download dialog", {
|
||||||
.should("exist");
|
|
||||||
cy.get('[role=dialog]')
|
|
||||||
.percySnapshotElement("App download dialog", {
|
|
||||||
widths: [640],
|
widths: [640],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("using find friends action should increase progress", () => {
|
it("using find friends action should increase progress", () => {
|
||||||
cy.get(".mx_ProgressBar").invoke("val").then((oldProgress) => {
|
cy.get(".mx_ProgressBar")
|
||||||
|
.invoke("val")
|
||||||
|
.then((oldProgress) => {
|
||||||
const findPeopleAction = cy.contains(".mx_UserOnboardingTask_action", "Find friends");
|
const findPeopleAction = cy.contains(".mx_UserOnboardingTask_action", "Find friends");
|
||||||
expect(findPeopleAction).to.exist;
|
expect(findPeopleAction).to.exist;
|
||||||
findPeopleAction.click();
|
findPeopleAction.click();
|
||||||
|
@ -84,10 +82,10 @@ describe("User Onboarding (new user)", () => {
|
||||||
cy.get(".mx_SendMessageComposer").type(`${message}!{enter}`);
|
cy.get(".mx_SendMessageComposer").type(`${message}!{enter}`);
|
||||||
cy.contains(".mx_MTextBody.mx_EventTile_content", message);
|
cy.contains(".mx_MTextBody.mx_EventTile_content", message);
|
||||||
cy.visit("/#/home");
|
cy.visit("/#/home");
|
||||||
cy.get('.mx_UserOnboardingPage').should('exist');
|
cy.get(".mx_UserOnboardingPage").should("exist");
|
||||||
cy.get('.mx_UserOnboardingButton').should('exist');
|
cy.get(".mx_UserOnboardingButton").should("exist");
|
||||||
cy.get('.mx_UserOnboardingList')
|
cy.get(".mx_UserOnboardingList")
|
||||||
.should('exist')
|
.should("exist")
|
||||||
.should(($list) => {
|
.should(($list) => {
|
||||||
const list = $list.get(0);
|
const list = $list.get(0);
|
||||||
expect(getComputedStyle(list).opacity).to.be.eq("1");
|
expect(getComputedStyle(list).opacity).to.be.eq("1");
|
||||||
|
|
|
@ -22,10 +22,10 @@ describe("User Onboarding (old user)", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, "Jane Doe");
|
cy.initTestUser(synapse, "Jane Doe");
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
win.localStorage.setItem("mx_registration_time", "2");
|
win.localStorage.setItem("mx_registration_time", "2");
|
||||||
});
|
});
|
||||||
cy.reload().then(() => {
|
cy.reload().then(() => {
|
||||||
|
@ -41,8 +41,8 @@ describe("User Onboarding (old user)", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("page and preference are hidden", () => {
|
it("page and preference are hidden", () => {
|
||||||
cy.get('.mx_UserOnboardingPage').should('not.exist');
|
cy.get(".mx_UserOnboardingPage").should("not.exist");
|
||||||
cy.get('.mx_UserOnboardingButton').should('not.exist');
|
cy.get(".mx_UserOnboardingButton").should("not.exist");
|
||||||
cy.openUserSettings("Preferences");
|
cy.openUserSettings("Preferences");
|
||||||
cy.contains("Show shortcut to welcome page above the room list").should("not.exist");
|
cy.contains("Show shortcut to welcome page above the room list").should("not.exist");
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe("UserView", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Violet");
|
cy.initTestUser(synapse, "Violet");
|
||||||
|
@ -36,7 +36,7 @@ describe("UserView", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the user view as expected", () => {
|
it("should render the user view as expected", () => {
|
||||||
cy.get<MatrixClient>("@bot").then(bot => {
|
cy.get<MatrixClient>("@bot").then((bot) => {
|
||||||
cy.visit(`/#/user/${bot.getUserId()}`);
|
cy.visit(`/#/user/${bot.getUserId()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { IWidget } from "matrix-widget-api";
|
||||||
|
|
||||||
import { SynapseInstance } from "../../plugins/synapsedocker";
|
import { SynapseInstance } from "../../plugins/synapsedocker";
|
||||||
|
|
||||||
const ROOM_NAME = 'Test Room';
|
const ROOM_NAME = "Test Room";
|
||||||
const WIDGET_ID = "fake-widget";
|
const WIDGET_ID = "fake-widget";
|
||||||
const WIDGET_HTML = `
|
const WIDGET_HTML = `
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -32,18 +32,18 @@ const WIDGET_HTML = `
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
describe('Widget Layout', () => {
|
describe("Widget Layout", () => {
|
||||||
let widgetUrl: string;
|
let widgetUrl: string;
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
let roomId: string;
|
let roomId: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Sally");
|
cy.initTestUser(synapse, "Sally");
|
||||||
});
|
});
|
||||||
cy.serveHtmlFile(WIDGET_HTML).then(url => {
|
cy.serveHtmlFile(WIDGET_HTML).then((url) => {
|
||||||
widgetUrl = url;
|
widgetUrl = url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -53,34 +53,38 @@ describe('Widget Layout', () => {
|
||||||
roomId = id;
|
roomId = id;
|
||||||
|
|
||||||
// setup widget via state event
|
// setup widget via state event
|
||||||
cy.getClient().then(async matrixClient => {
|
cy.getClient()
|
||||||
|
.then(async (matrixClient) => {
|
||||||
const content: IWidget = {
|
const content: IWidget = {
|
||||||
id: WIDGET_ID,
|
id: WIDGET_ID,
|
||||||
creatorUserId: 'somebody',
|
creatorUserId: "somebody",
|
||||||
type: 'widget',
|
type: "widget",
|
||||||
name: 'widget',
|
name: "widget",
|
||||||
url: widgetUrl,
|
url: widgetUrl,
|
||||||
};
|
};
|
||||||
await matrixClient.sendStateEvent(roomId, 'im.vector.modular.widgets', content, WIDGET_ID);
|
await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, WIDGET_ID);
|
||||||
}).as('widgetEventSent');
|
})
|
||||||
|
.as("widgetEventSent");
|
||||||
|
|
||||||
// set initial layout
|
// set initial layout
|
||||||
cy.getClient().then(async matrixClient => {
|
cy.getClient()
|
||||||
|
.then(async (matrixClient) => {
|
||||||
const content = {
|
const content = {
|
||||||
widgets: {
|
widgets: {
|
||||||
[WIDGET_ID]: {
|
[WIDGET_ID]: {
|
||||||
container: 'top', index: 1, width: 100, height: 0,
|
container: "top",
|
||||||
|
index: 1,
|
||||||
|
width: 100,
|
||||||
|
height: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await matrixClient.sendStateEvent(roomId, 'io.element.widgets.layout', content, "");
|
await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, "");
|
||||||
}).as('layoutEventSent');
|
})
|
||||||
|
.as("layoutEventSent");
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.all([
|
cy.all([cy.get<string>("@widgetEventSent"), cy.get<string>("@layoutEventSent")]).then(() => {
|
||||||
cy.get<string>("@widgetEventSent"),
|
|
||||||
cy.get<string>("@layoutEventSent"),
|
|
||||||
]).then(() => {
|
|
||||||
// open the room
|
// open the room
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
});
|
});
|
||||||
|
@ -91,31 +95,34 @@ describe('Widget Layout', () => {
|
||||||
cy.stopWebServers();
|
cy.stopWebServers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('manually resize the height of the top container layout', () => {
|
it("manually resize the height of the top container layout", () => {
|
||||||
cy.get('iframe[title="widget"]').invoke('height').should('be.lessThan', 250);
|
cy.get('iframe[title="widget"]').invoke("height").should("be.lessThan", 250);
|
||||||
|
|
||||||
cy.get('.mx_AppsContainer_resizerHandle')
|
cy.get(".mx_AppsContainer_resizerHandle")
|
||||||
.trigger('mousedown')
|
.trigger("mousedown")
|
||||||
.trigger('mousemove', { clientX: 0, clientY: 550, force: true })
|
.trigger("mousemove", { clientX: 0, clientY: 550, force: true })
|
||||||
.trigger('mouseup', { clientX: 0, clientY: 550, force: true });
|
.trigger("mouseup", { clientX: 0, clientY: 550, force: true });
|
||||||
|
|
||||||
cy.get('iframe[title="widget"]').invoke('height').should('be.greaterThan', 400);
|
cy.get('iframe[title="widget"]').invoke("height").should("be.greaterThan", 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('programatically resize the height of the top container layout', () => {
|
it("programatically resize the height of the top container layout", () => {
|
||||||
cy.get('iframe[title="widget"]').invoke('height').should('be.lessThan', 250);
|
cy.get('iframe[title="widget"]').invoke("height").should("be.lessThan", 250);
|
||||||
|
|
||||||
cy.getClient().then(async matrixClient => {
|
cy.getClient().then(async (matrixClient) => {
|
||||||
const content = {
|
const content = {
|
||||||
widgets: {
|
widgets: {
|
||||||
[WIDGET_ID]: {
|
[WIDGET_ID]: {
|
||||||
container: 'top', index: 1, width: 100, height: 100,
|
container: "top",
|
||||||
|
index: 1,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await matrixClient.sendStateEvent(roomId, 'io.element.widgets.layout', content, "");
|
await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, "");
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('iframe[title="widget"]').invoke('height').should('be.greaterThan', 400);
|
cy.get('iframe[title="widget"]').invoke("height").should("be.greaterThan", 400);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,8 +67,8 @@ const WIDGET_HTML = `
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function openStickerPicker() {
|
function openStickerPicker() {
|
||||||
cy.get('.mx_MessageComposer_buttonMenu').click();
|
cy.get(".mx_MessageComposer_buttonMenu").click();
|
||||||
cy.get('#stickersButton').click();
|
cy.get("#stickersButton").click();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendStickerFromPicker() {
|
function sendStickerFromPicker() {
|
||||||
|
@ -76,18 +76,16 @@ function sendStickerFromPicker() {
|
||||||
// to use `chromeWebSecurity: false` in our cypress config. Not even cy.origin() can
|
// to use `chromeWebSecurity: false` in our cypress config. Not even cy.origin() can
|
||||||
// break into the iframe for us :(
|
// break into the iframe for us :(
|
||||||
cy.accessIframe(`iframe[title="${STICKER_PICKER_WIDGET_NAME}"]`).within({}, () => {
|
cy.accessIframe(`iframe[title="${STICKER_PICKER_WIDGET_NAME}"]`).within({}, () => {
|
||||||
cy.get("#sendsticker").should('exist').click();
|
cy.get("#sendsticker").should("exist").click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sticker picker should close itself after sending.
|
// Sticker picker should close itself after sending.
|
||||||
cy.get(".mx_AppTileFullWidth#stickers").should('not.exist');
|
cy.get(".mx_AppTileFullWidth#stickers").should("not.exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectTimelineSticker(roomId: string) {
|
function expectTimelineSticker(roomId: string) {
|
||||||
// Make sure it's in the right room
|
// Make sure it's in the right room
|
||||||
cy.get('.mx_EventTile_sticker > a')
|
cy.get(".mx_EventTile_sticker > a").should("have.attr", "href").and("include", `/${roomId}/`);
|
||||||
.should("have.attr", "href")
|
|
||||||
.and("include", `/${roomId}/`);
|
|
||||||
|
|
||||||
// Make sure the image points at the sticker image
|
// Make sure the image points at the sticker image
|
||||||
cy.get<HTMLImageElement>(`img[alt="${STICKER_NAME}"]`)
|
cy.get<HTMLImageElement>(`img[alt="${STICKER_NAME}"]`)
|
||||||
|
@ -107,12 +105,12 @@ describe("Stickers", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Sally");
|
cy.initTestUser(synapse, "Sally");
|
||||||
});
|
});
|
||||||
cy.serveHtmlFile(WIDGET_HTML).then(url => {
|
cy.serveHtmlFile(WIDGET_HTML).then((url) => {
|
||||||
stickerPickerUrl = url;
|
stickerPickerUrl = url;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -122,7 +120,7 @@ describe("Stickers", () => {
|
||||||
cy.stopWebServers();
|
cy.stopWebServers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send a sticker to multiple rooms', () => {
|
it("should send a sticker to multiple rooms", () => {
|
||||||
cy.createRoom({
|
cy.createRoom({
|
||||||
name: ROOM_NAME_1,
|
name: ROOM_NAME_1,
|
||||||
}).as("roomId1");
|
}).as("roomId1");
|
||||||
|
|
|
@ -57,7 +57,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
function eventsInIntendedState(evList) {
|
function eventsInIntendedState(evList) {
|
||||||
const widgetPresent = evList.some((ev) => {
|
const widgetPresent = evList.some((ev) => {
|
||||||
return ev.getContent() && ev.getContent()['id'] === widgetId;
|
return ev.getContent() && ev.getContent()["id"] === widgetId;
|
||||||
});
|
});
|
||||||
if (add) {
|
if (add) {
|
||||||
return widgetPresent;
|
return widgetPresent;
|
||||||
|
@ -68,7 +68,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str
|
||||||
|
|
||||||
const room = matrixClient.getRoom(roomId);
|
const room = matrixClient.getRoom(roomId);
|
||||||
|
|
||||||
const startingWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
const startingWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
|
||||||
if (eventsInIntendedState(startingWidgetEvents)) {
|
if (eventsInIntendedState(startingWidgetEvents)) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
|
@ -77,7 +77,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str
|
||||||
function onRoomStateEvents(ev: MatrixEvent) {
|
function onRoomStateEvents(ev: MatrixEvent) {
|
||||||
if (ev.getRoomId() !== roomId || ev.getType() !== "im.vector.modular.widgets") return;
|
if (ev.getRoomId() !== roomId || ev.getType() !== "im.vector.modular.widgets") return;
|
||||||
|
|
||||||
const currentWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
const currentWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
|
||||||
|
|
||||||
if (eventsInIntendedState(currentWidgetEvents)) {
|
if (eventsInIntendedState(currentWidgetEvents)) {
|
||||||
matrixClient.removeListener(win.matrixcs.RoomStateEvent.Events, onRoomStateEvents);
|
matrixClient.removeListener(win.matrixcs.RoomStateEvent.Events, onRoomStateEvents);
|
||||||
|
@ -95,35 +95,39 @@ describe("Widget PIP", () => {
|
||||||
let bot: MatrixClient;
|
let bot: MatrixClient;
|
||||||
let demoWidgetUrl: string;
|
let demoWidgetUrl: string;
|
||||||
|
|
||||||
function roomCreateAddWidgetPip(userRemove: 'leave' | 'kick' | 'ban') {
|
function roomCreateAddWidgetPip(userRemove: "leave" | "kick" | "ban") {
|
||||||
cy.createRoom({
|
cy.createRoom({
|
||||||
name: ROOM_NAME,
|
name: ROOM_NAME,
|
||||||
invite: [bot.getUserId()],
|
invite: [bot.getUserId()],
|
||||||
}).then(roomId => {
|
}).then((roomId) => {
|
||||||
// sets bot to Admin and user to Moderator
|
// sets bot to Admin and user to Moderator
|
||||||
cy.getClient().then(matrixClient => {
|
cy.getClient()
|
||||||
return matrixClient.sendStateEvent(roomId, 'm.room.power_levels', {
|
.then((matrixClient) => {
|
||||||
|
return matrixClient.sendStateEvent(roomId, "m.room.power_levels", {
|
||||||
users: {
|
users: {
|
||||||
[user.userId]: 50,
|
[user.userId]: 50,
|
||||||
[bot.getUserId()]: 100,
|
[bot.getUserId()]: 100,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}).as('powerLevelsChanged');
|
})
|
||||||
|
.as("powerLevelsChanged");
|
||||||
|
|
||||||
// bot joins the room
|
// bot joins the room
|
||||||
cy.botJoinRoom(bot, roomId).as('botJoined');
|
cy.botJoinRoom(bot, roomId).as("botJoined");
|
||||||
|
|
||||||
// setup widget via state event
|
// setup widget via state event
|
||||||
cy.getClient().then(async matrixClient => {
|
cy.getClient()
|
||||||
|
.then(async (matrixClient) => {
|
||||||
const content: IWidget = {
|
const content: IWidget = {
|
||||||
id: DEMO_WIDGET_ID,
|
id: DEMO_WIDGET_ID,
|
||||||
creatorUserId: 'somebody',
|
creatorUserId: "somebody",
|
||||||
type: DEMO_WIDGET_TYPE,
|
type: DEMO_WIDGET_TYPE,
|
||||||
name: DEMO_WIDGET_NAME,
|
name: DEMO_WIDGET_NAME,
|
||||||
url: demoWidgetUrl,
|
url: demoWidgetUrl,
|
||||||
};
|
};
|
||||||
await matrixClient.sendStateEvent(roomId, 'im.vector.modular.widgets', content, DEMO_WIDGET_ID);
|
await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, DEMO_WIDGET_ID);
|
||||||
}).as('widgetEventSent');
|
})
|
||||||
|
.as("widgetEventSent");
|
||||||
|
|
||||||
// open the room
|
// open the room
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
|
@ -133,7 +137,7 @@ describe("Widget PIP", () => {
|
||||||
cy.get<string>("@botJoined"),
|
cy.get<string>("@botJoined"),
|
||||||
cy.get<string>("@widgetEventSent"),
|
cy.get<string>("@widgetEventSent"),
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
cy.window().then(async win => {
|
cy.window().then(async (win) => {
|
||||||
// wait for widget state event
|
// wait for widget state event
|
||||||
await waitForRoomWidget(win, DEMO_WIDGET_ID, roomId, true);
|
await waitForRoomWidget(win, DEMO_WIDGET_ID, roomId, true);
|
||||||
|
|
||||||
|
@ -145,15 +149,17 @@ describe("Widget PIP", () => {
|
||||||
|
|
||||||
// checks that widget is opened in pip
|
// checks that widget is opened in pip
|
||||||
cy.accessIframe(`iframe[title="${DEMO_WIDGET_NAME}"]`).within({}, () => {
|
cy.accessIframe(`iframe[title="${DEMO_WIDGET_NAME}"]`).within({}, () => {
|
||||||
cy.get("#demo").should('exist').then(async () => {
|
cy.get("#demo")
|
||||||
|
.should("exist")
|
||||||
|
.then(async () => {
|
||||||
const userId = user.userId;
|
const userId = user.userId;
|
||||||
if (userRemove == 'leave') {
|
if (userRemove == "leave") {
|
||||||
cy.getClient().then(async matrixClient => {
|
cy.getClient().then(async (matrixClient) => {
|
||||||
await matrixClient.leave(roomId);
|
await matrixClient.leave(roomId);
|
||||||
});
|
});
|
||||||
} else if (userRemove == 'kick') {
|
} else if (userRemove == "kick") {
|
||||||
await bot.kick(roomId, userId);
|
await bot.kick(roomId, userId);
|
||||||
} else if (userRemove == 'ban') {
|
} else if (userRemove == "ban") {
|
||||||
await bot.ban(roomId, userId);
|
await bot.ban(roomId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,17 +173,17 @@ describe("Widget PIP", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then((data) => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
||||||
cy.initTestUser(synapse, "Mike").then(_user => {
|
cy.initTestUser(synapse, "Mike").then((_user) => {
|
||||||
user = _user;
|
user = _user;
|
||||||
});
|
});
|
||||||
cy.getBot(synapse, { displayName: "Bot", autoAcceptInvites: false }).then(_bot => {
|
cy.getBot(synapse, { displayName: "Bot", autoAcceptInvites: false }).then((_bot) => {
|
||||||
bot = _bot;
|
bot = _bot;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cy.serveHtmlFile(DEMO_WIDGET_HTML).then(url => {
|
cy.serveHtmlFile(DEMO_WIDGET_HTML).then((url) => {
|
||||||
demoWidgetUrl = url;
|
demoWidgetUrl = url;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -187,15 +193,15 @@ describe("Widget PIP", () => {
|
||||||
cy.stopWebServers();
|
cy.stopWebServers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be closed on leave', () => {
|
it("should be closed on leave", () => {
|
||||||
roomCreateAddWidgetPip('leave');
|
roomCreateAddWidgetPip("leave");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be closed on kick', () => {
|
it("should be closed on kick", () => {
|
||||||
roomCreateAddWidgetPip('kick');
|
roomCreateAddWidgetPip("kick");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be closed on ban', () => {
|
it("should be closed on ban", () => {
|
||||||
roomCreateAddWidgetPip('ban');
|
roomCreateAddWidgetPip("ban");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,7 +42,8 @@ export function dockerRun(opts: {
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
"run",
|
"run",
|
||||||
"--name", `${opts.containerName}-${crypto.randomBytes(4).toString("hex")}`,
|
"--name",
|
||||||
|
`${opts.containerName}-${crypto.randomBytes(4).toString("hex")}`,
|
||||||
"-d",
|
"-d",
|
||||||
...params,
|
...params,
|
||||||
opts.image,
|
opts.image,
|
||||||
|
@ -58,15 +59,13 @@ export function dockerRun(opts: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dockerExec(args: {
|
export function dockerExec(args: { containerId: string; params: string[] }): Promise<void> {
|
||||||
containerId: string;
|
|
||||||
params: string[];
|
|
||||||
}): Promise<void> {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
childProcess.execFile("docker", [
|
childProcess.execFile(
|
||||||
"exec", args.containerId,
|
"docker",
|
||||||
...args.params,
|
["exec", args.containerId, ...args.params],
|
||||||
], { encoding: 'utf8' }, (err, stdout, stderr) => {
|
{ encoding: "utf8" },
|
||||||
|
(err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(stdout);
|
console.log(stdout);
|
||||||
console.log(stderr);
|
console.log(stderr);
|
||||||
|
@ -74,7 +73,8 @@ export function dockerExec(args: {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,58 +87,45 @@ export async function dockerLogs(args: {
|
||||||
const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore";
|
const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore";
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
childProcess.spawn("docker", [
|
childProcess
|
||||||
"logs",
|
.spawn("docker", ["logs", args.containerId], {
|
||||||
args.containerId,
|
|
||||||
], {
|
|
||||||
stdio: ["ignore", stdoutFile, stderrFile],
|
stdio: ["ignore", stdoutFile, stderrFile],
|
||||||
}).once('close', resolve);
|
})
|
||||||
|
.once("close", resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (args.stdoutFile) await fse.close(<number>stdoutFile);
|
if (args.stdoutFile) await fse.close(<number>stdoutFile);
|
||||||
if (args.stderrFile) await fse.close(<number>stderrFile);
|
if (args.stderrFile) await fse.close(<number>stderrFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dockerStop(args: {
|
export function dockerStop(args: { containerId: string }): Promise<void> {
|
||||||
containerId: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
childProcess.execFile('docker', [
|
childProcess.execFile("docker", ["stop", args.containerId], (err) => {
|
||||||
"stop",
|
|
||||||
args.containerId,
|
|
||||||
], err => {
|
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dockerRm(args: {
|
export function dockerRm(args: { containerId: string }): Promise<void> {
|
||||||
containerId: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
childProcess.execFile('docker', [
|
childProcess.execFile("docker", ["rm", args.containerId], (err) => {
|
||||||
"rm",
|
|
||||||
args.containerId,
|
|
||||||
], err => {
|
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dockerIp(args: {
|
export function dockerIp(args: { containerId: string }): Promise<string> {
|
||||||
containerId: string;
|
|
||||||
}): Promise<string> {
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
childProcess.execFile('docker', [
|
childProcess.execFile(
|
||||||
"inspect",
|
"docker",
|
||||||
"-f", "{{ .NetworkSettings.IPAddress }}",
|
["inspect", "-f", "{{ .NetworkSettings.IPAddress }}", args.containerId],
|
||||||
args.containerId,
|
(err, stdout) => {
|
||||||
], (err, stdout) => {
|
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else resolve(stdout.trim());
|
else resolve(stdout.trim());
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,7 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<
|
||||||
const postgresId = await dockerRun({
|
const postgresId = await dockerRun({
|
||||||
image: "postgres",
|
image: "postgres",
|
||||||
containerName: "react-sdk-cypress-sliding-sync-postgres",
|
containerName: "react-sdk-cypress-sliding-sync-postgres",
|
||||||
params: [
|
params: ["--rm", "-e", `POSTGRES_PASSWORD=${PG_PASSWORD}`],
|
||||||
"--rm",
|
|
||||||
"-e", `POSTGRES_PASSWORD=${PG_PASSWORD}`,
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const postgresIp = await dockerIp({ containerId: postgresId });
|
const postgresIp = await dockerIp({ containerId: postgresId });
|
||||||
|
@ -54,14 +51,11 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<
|
||||||
const waitTimeMillis = 30000;
|
const waitTimeMillis = 30000;
|
||||||
const startTime = new Date().getTime();
|
const startTime = new Date().getTime();
|
||||||
let lastErr: Error;
|
let lastErr: Error;
|
||||||
while ((new Date().getTime() - startTime) < waitTimeMillis) {
|
while (new Date().getTime() - startTime < waitTimeMillis) {
|
||||||
try {
|
try {
|
||||||
await dockerExec({
|
await dockerExec({
|
||||||
containerId: postgresId,
|
containerId: postgresId,
|
||||||
params: [
|
params: ["pg_isready", "-U", "postgres"],
|
||||||
"pg_isready",
|
|
||||||
"-U", "postgres",
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
lastErr = null;
|
lastErr = null;
|
||||||
break;
|
break;
|
||||||
|
@ -82,10 +76,14 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<
|
||||||
containerName: "react-sdk-cypress-sliding-sync-proxy",
|
containerName: "react-sdk-cypress-sliding-sync-proxy",
|
||||||
params: [
|
params: [
|
||||||
"--rm",
|
"--rm",
|
||||||
"-p", `${port}:8008/tcp`,
|
"-p",
|
||||||
"-e", "SYNCV3_SECRET=bwahahaha",
|
`${port}:8008/tcp`,
|
||||||
"-e", `SYNCV3_SERVER=http://${synapseIp}:8008`,
|
"-e",
|
||||||
"-e", `SYNCV3_DB=user=postgres dbname=postgres password=${PG_PASSWORD} host=${postgresIp} sslmode=disable`,
|
"SYNCV3_SECRET=bwahahaha",
|
||||||
|
"-e",
|
||||||
|
`SYNCV3_SERVER=http://${synapseIp}:8008`,
|
||||||
|
"-e",
|
||||||
|
`SYNCV3_DB=user=postgres dbname=postgres password=${PG_PASSWORD} host=${postgresIp} sslmode=disable`,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
console.log(new Date(), "started!");
|
console.log(new Date(), "started!");
|
||||||
|
|
|
@ -54,11 +54,11 @@ async function cfgDirFromTemplate(template: string): Promise<SynapseConfig> {
|
||||||
if (!stats?.isDirectory) {
|
if (!stats?.isDirectory) {
|
||||||
throw new Error(`No such template: ${template}`);
|
throw new Error(`No such template: ${template}`);
|
||||||
}
|
}
|
||||||
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), 'react-sdk-synapsedocker-'));
|
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-synapsedocker-"));
|
||||||
|
|
||||||
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
|
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
|
||||||
console.log(`Copy ${templateDir} -> ${tempDir}`);
|
console.log(`Copy ${templateDir} -> ${tempDir}`);
|
||||||
await fse.copy(templateDir, tempDir, { filter: f => path.basename(f) !== 'homeserver.yaml' });
|
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== "homeserver.yaml" });
|
||||||
|
|
||||||
const registrationSecret = randB64Bytes(16);
|
const registrationSecret = randB64Bytes(16);
|
||||||
const macaroonSecret = randB64Bytes(16);
|
const macaroonSecret = randB64Bytes(16);
|
||||||
|
@ -102,11 +102,7 @@ async function synapseStart(template: string): Promise<SynapseInstance> {
|
||||||
const synapseId = await dockerRun({
|
const synapseId = await dockerRun({
|
||||||
image: "matrixdotorg/synapse:develop",
|
image: "matrixdotorg/synapse:develop",
|
||||||
containerName: `react-sdk-cypress-synapse`,
|
containerName: `react-sdk-cypress-synapse`,
|
||||||
params: [
|
params: ["--rm", "-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`],
|
||||||
"--rm",
|
|
||||||
"-v", `${synCfg.configDir}:/data`,
|
|
||||||
"-p", `${synCfg.port}:8008/tcp`,
|
|
||||||
],
|
|
||||||
cmd: "run",
|
cmd: "run",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -117,9 +113,12 @@ async function synapseStart(template: string): Promise<SynapseInstance> {
|
||||||
containerId: synapseId,
|
containerId: synapseId,
|
||||||
params: [
|
params: [
|
||||||
"curl",
|
"curl",
|
||||||
"--connect-timeout", "30",
|
"--connect-timeout",
|
||||||
"--retry", "30",
|
"30",
|
||||||
"--retry-delay", "1",
|
"--retry",
|
||||||
|
"30",
|
||||||
|
"--retry-delay",
|
||||||
|
"1",
|
||||||
"--retry-all-errors",
|
"--retry-all-errors",
|
||||||
"--silent",
|
"--silent",
|
||||||
"http://localhost:8008/health",
|
"http://localhost:8008/health",
|
||||||
|
|
|
@ -7,7 +7,7 @@ public_baseurl: http://localhost:8008/
|
||||||
listeners:
|
listeners:
|
||||||
- port: 8008
|
- port: 8008
|
||||||
tls: false
|
tls: false
|
||||||
bind_addresses: ['::']
|
bind_addresses: ["::"]
|
||||||
type: http
|
type: http
|
||||||
x_forwarded: true
|
x_forwarded: true
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ public_baseurl: "{{PUBLIC_BASEURL}}"
|
||||||
listeners:
|
listeners:
|
||||||
- port: 8008
|
- port: 8008
|
||||||
tls: false
|
tls: false
|
||||||
bind_addresses: ['::']
|
bind_addresses: ["::"]
|
||||||
type: http
|
type: http
|
||||||
x_forwarded: true
|
x_forwarded: true
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Test Privacy policy</title>
|
<title>Test Privacy policy</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% if has_consented %}
|
{% if has_consented %}
|
||||||
<p>
|
<p>Thank you, you've already accepted the license.</p>
|
||||||
Thank you, you've already accepted the license.
|
|
||||||
</p>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>Please accept the license!</p>
|
||||||
Please accept the license!
|
|
||||||
</p>
|
|
||||||
<form method="post" action="consent">
|
<form method="post" action="consent">
|
||||||
<input type="hidden" name="v" value="{{version}}" />
|
<input type="hidden" name="v" value="{{version}}" />
|
||||||
<input type="hidden" name="u" value="{{user}}" />
|
<input type="hidden" name="u" value="{{user}}" />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Test Privacy policy</title>
|
<title>Test Privacy policy</title>
|
||||||
|
|
|
@ -4,7 +4,7 @@ public_baseurl: "{{PUBLIC_BASEURL}}"
|
||||||
listeners:
|
listeners:
|
||||||
- port: 8008
|
- port: 8008
|
||||||
tls: false
|
tls: false
|
||||||
bind_addresses: ['::']
|
bind_addresses: ["::"]
|
||||||
type: http
|
type: http
|
||||||
x_forwarded: true
|
x_forwarded: true
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import * as net from "net";
|
import * as net from "net";
|
||||||
|
|
||||||
export async function getFreePort(): Promise<number> {
|
export async function getFreePort(): Promise<number> {
|
||||||
return new Promise<number>(resolve => {
|
return new Promise<number>((resolve) => {
|
||||||
const srv = net.createServer();
|
const srv = net.createServer();
|
||||||
srv.listen(0, () => {
|
srv.listen(0, () => {
|
||||||
const port = (<net.AddressInfo>srv.address()).port;
|
const port = (<net.AddressInfo>srv.address()).port;
|
||||||
|
|
|
@ -32,7 +32,7 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("tweakConfig", (tweaks: Record<string, any>): Chainable<AUTWindow> => {
|
Cypress.Commands.add("tweakConfig", (tweaks: Record<string, any>): Chainable<AUTWindow> => {
|
||||||
return cy.window().then(win => {
|
return cy.window().then((win) => {
|
||||||
// note: we can't *set* the object because the window version is effectively a pointer.
|
// note: we can't *set* the object because the window version is effectively a pointer.
|
||||||
for (const [k, v] of Object.entries(tweaks)) {
|
for (const [k, v] of Object.entries(tweaks)) {
|
||||||
// @ts-ignore - for some reason it's not picking up on global.d.ts types.
|
// @ts-ignore - for some reason it's not picking up on global.d.ts types.
|
||||||
|
|
|
@ -24,10 +24,10 @@ import Chainable = Cypress.Chainable;
|
||||||
|
|
||||||
function terminalLog(violations: axe.Result[]): void {
|
function terminalLog(violations: axe.Result[]): void {
|
||||||
cy.task(
|
cy.task(
|
||||||
'log',
|
"log",
|
||||||
`${violations.length} accessibility violation${
|
`${violations.length} accessibility violation${violations.length === 1 ? "" : "s"} ${
|
||||||
violations.length === 1 ? '' : 's'
|
violations.length === 1 ? "was" : "were"
|
||||||
} ${violations.length === 1 ? 'was' : 'were'} detected`,
|
} detected`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// pluck specific keys to keep the table readable
|
// pluck specific keys to keep the table readable
|
||||||
|
@ -38,24 +38,32 @@ function terminalLog(violations: axe.Result[]): void {
|
||||||
nodes: nodes.length,
|
nodes: nodes.length,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
cy.task('table', violationData);
|
cy.task("table", violationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.overwrite("checkA11y", (
|
Cypress.Commands.overwrite(
|
||||||
|
"checkA11y",
|
||||||
|
(
|
||||||
originalFn: Chainable["checkA11y"],
|
originalFn: Chainable["checkA11y"],
|
||||||
context?: string | Node | axe.ContextObject | undefined,
|
context?: string | Node | axe.ContextObject | undefined,
|
||||||
options: Options = {},
|
options: Options = {},
|
||||||
violationCallback?: ((violations: axe.Result[]) => void) | undefined,
|
violationCallback?: ((violations: axe.Result[]) => void) | undefined,
|
||||||
skipFailures?: boolean,
|
skipFailures?: boolean,
|
||||||
): void => {
|
): void => {
|
||||||
return originalFn(context, {
|
return originalFn(
|
||||||
|
context,
|
||||||
|
{
|
||||||
...options,
|
...options,
|
||||||
rules: {
|
rules: {
|
||||||
// Disable contrast checking for now as we have too many issues with it
|
// Disable contrast checking for now as we have too many issues with it
|
||||||
'color-contrast': {
|
"color-contrast": {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
...options.rules,
|
...options.rules,
|
||||||
},
|
},
|
||||||
}, violationCallback ?? terminalLog, skipFailures);
|
},
|
||||||
});
|
violationCallback ?? terminalLog,
|
||||||
|
skipFailures,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -77,9 +77,9 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts):
|
||||||
opts = Object.assign({}, defaultCreateBotOptions, opts);
|
opts = Object.assign({}, defaultCreateBotOptions, opts);
|
||||||
const username = Cypress._.uniqueId("userId_");
|
const username = Cypress._.uniqueId("userId_");
|
||||||
const password = Cypress._.uniqueId("password_");
|
const password = Cypress._.uniqueId("password_");
|
||||||
return cy.registerUser(synapse, username, password, opts.displayName).then(credentials => {
|
return cy.registerUser(synapse, username, password, opts.displayName).then((credentials) => {
|
||||||
cy.log(`Registered bot user ${username} with displayname ${opts.displayName}`);
|
cy.log(`Registered bot user ${username} with displayname ${opts.displayName}`);
|
||||||
return cy.window({ log: false }).then(win => {
|
return cy.window({ log: false }).then((win) => {
|
||||||
const cli = new win.matrixcs.MatrixClient({
|
const cli = new win.matrixcs.MatrixClient({
|
||||||
baseUrl: synapse.baseUrl,
|
baseUrl: synapse.baseUrl,
|
||||||
userId: credentials.userId,
|
userId: credentials.userId,
|
||||||
|
@ -103,12 +103,17 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts):
|
||||||
}
|
}
|
||||||
|
|
||||||
return cy.wrap(
|
return cy.wrap(
|
||||||
cli.initCrypto()
|
cli
|
||||||
|
.initCrypto()
|
||||||
.then(() => cli.setGlobalErrorOnUnknownDevices(false))
|
.then(() => cli.setGlobalErrorOnUnknownDevices(false))
|
||||||
.then(() => cli.startClient())
|
.then(() => cli.startClient())
|
||||||
.then(() => cli.bootstrapCrossSigning({
|
.then(() =>
|
||||||
authUploadDeviceSigningKeys: async func => { await func({}); },
|
cli.bootstrapCrossSigning({
|
||||||
}))
|
authUploadDeviceSigningKeys: async (func) => {
|
||||||
|
await func({});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
.then(() => cli),
|
.then(() => cli),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -129,13 +134,15 @@ Cypress.Commands.add("botJoinRoomByName", (cli: MatrixClient, roomName: string):
|
||||||
return cy.wrap(Promise.reject(`Bot room join failed. Cannot find room '${roomName}'`));
|
return cy.wrap(Promise.reject(`Bot room join failed. Cannot find room '${roomName}'`));
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("botSendMessage", (
|
Cypress.Commands.add(
|
||||||
cli: MatrixClient,
|
"botSendMessage",
|
||||||
roomId: string,
|
(cli: MatrixClient, roomId: string, message: string): Chainable<ISendEventResponse> => {
|
||||||
message: string,
|
return cy.wrap(
|
||||||
): Chainable<ISendEventResponse> => {
|
cli.sendMessage(roomId, {
|
||||||
return cy.wrap(cli.sendMessage(roomId, {
|
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: message,
|
body: message,
|
||||||
}), { log: false });
|
}),
|
||||||
});
|
{ log: false },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -66,7 +66,7 @@ declare global {
|
||||||
roomId: string,
|
roomId: string,
|
||||||
threadId: string | null,
|
threadId: string | null,
|
||||||
eventType: string,
|
eventType: string,
|
||||||
content: IContent
|
content: IContent,
|
||||||
): Chainable<ISendEventResponse>;
|
): Chainable<ISendEventResponse>;
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
|
@ -89,10 +89,7 @@ declare global {
|
||||||
* can be sent to XMLHttpRequest.send (typically a File). Under node.js,
|
* can be sent to XMLHttpRequest.send (typically a File). Under node.js,
|
||||||
* a a Buffer, String or ReadStream.
|
* a a Buffer, String or ReadStream.
|
||||||
*/
|
*/
|
||||||
uploadContent(
|
uploadContent(file: FileType, opts?: UploadOpts): Chainable<Awaited<Upload["promise"]>>;
|
||||||
file: FileType,
|
|
||||||
opts?: UploadOpts,
|
|
||||||
): Chainable<Awaited<Upload["promise"]>>;
|
|
||||||
/**
|
/**
|
||||||
* Turn an MXC URL into an HTTP one. <strong>This method is experimental and
|
* Turn an MXC URL into an HTTP one. <strong>This method is experimental and
|
||||||
* may change.</strong>
|
* may change.</strong>
|
||||||
|
@ -133,23 +130,24 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("getClient", (): Chainable<MatrixClient | undefined> => {
|
Cypress.Commands.add("getClient", (): Chainable<MatrixClient | undefined> => {
|
||||||
return cy.window({ log: false }).then(win => win.mxMatrixClientPeg.matrixClient);
|
return cy.window({ log: false }).then((win) => win.mxMatrixClientPeg.matrixClient);
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("getDmRooms", (userId: string): Chainable<string[]> => {
|
Cypress.Commands.add("getDmRooms", (userId: string): Chainable<string[]> => {
|
||||||
return cy.getClient()
|
return cy
|
||||||
.then(cli => cli.getAccountData("m.direct")?.getContent<Record<string, string[]>>())
|
.getClient()
|
||||||
.then(dmRoomMap => dmRoomMap[userId] ?? []);
|
.then((cli) => cli.getAccountData("m.direct")?.getContent<Record<string, string[]>>())
|
||||||
|
.then((dmRoomMap) => dmRoomMap[userId] ?? []);
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("createRoom", (options: ICreateRoomOpts): Chainable<string> => {
|
Cypress.Commands.add("createRoom", (options: ICreateRoomOpts): Chainable<string> => {
|
||||||
return cy.window({ log: false }).then(async win => {
|
return cy.window({ log: false }).then(async (win) => {
|
||||||
const cli = win.mxMatrixClientPeg.matrixClient;
|
const cli = win.mxMatrixClientPeg.matrixClient;
|
||||||
const resp = await cli.createRoom(options);
|
const resp = await cli.createRoom(options);
|
||||||
const roomId = resp.room_id;
|
const roomId = resp.room_id;
|
||||||
|
|
||||||
if (!cli.getRoom(roomId)) {
|
if (!cli.getRoom(roomId)) {
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>((resolve) => {
|
||||||
const onRoom = (room: Room) => {
|
const onRoom = (room: Room) => {
|
||||||
if (room.roomId === roomId) {
|
if (room.roomId === roomId) {
|
||||||
cli.off(win.matrixcs.ClientEvent.Room, onRoom);
|
cli.off(win.matrixcs.ClientEvent.Room, onRoom);
|
||||||
|
@ -168,7 +166,7 @@ Cypress.Commands.add("createSpace", (options: ICreateRoomOpts): Chainable<string
|
||||||
return cy.createRoom({
|
return cy.createRoom({
|
||||||
...options,
|
...options,
|
||||||
creation_content: {
|
creation_content: {
|
||||||
"type": "m.space",
|
type: "m.space",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -185,16 +183,14 @@ Cypress.Commands.add("setAccountData", (type: string, data: object): Chainable<{
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("sendEvent", (
|
Cypress.Commands.add(
|
||||||
roomId: string,
|
"sendEvent",
|
||||||
threadId: string | null,
|
(roomId: string, threadId: string | null, eventType: string, content: IContent): Chainable<ISendEventResponse> => {
|
||||||
eventType: string,
|
|
||||||
content: IContent,
|
|
||||||
): Chainable<ISendEventResponse> => {
|
|
||||||
return cy.getClient().then(async (cli: MatrixClient) => {
|
return cy.getClient().then(async (cli: MatrixClient) => {
|
||||||
return cli.sendEvent(roomId, threadId, eventType, content);
|
return cli.sendEvent(roomId, threadId, eventType, content);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => {
|
Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => {
|
||||||
return cy.getClient().then(async (cli: MatrixClient) => {
|
return cy.getClient().then(async (cli: MatrixClient) => {
|
||||||
|
@ -215,13 +211,15 @@ Cypress.Commands.add("setAvatarUrl", (url: string): Chainable<{}> => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("bootstrapCrossSigning", () => {
|
Cypress.Commands.add("bootstrapCrossSigning", () => {
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({
|
win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: async func => { await func({}); },
|
authUploadDeviceSigningKeys: async (func) => {
|
||||||
|
await func({});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("joinRoom", (roomIdOrAlias: string): Chainable<Room> => {
|
Cypress.Commands.add("joinRoom", (roomIdOrAlias: string): Chainable<Room> => {
|
||||||
return cy.getClient().then(cli => cli.joinRoom(roomIdOrAlias));
|
return cy.getClient().then((cli) => cli.joinRoom(roomIdOrAlias));
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,7 +41,7 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("mockClipboard", () => {
|
Cypress.Commands.add("mockClipboard", () => {
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
win.navigator.clipboard.writeText = (text) => {
|
win.navigator.clipboard.writeText = (text) => {
|
||||||
copyText = text;
|
copyText = text;
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
|
@ -33,7 +33,7 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("getComposer", (isRightPanel?: boolean): Chainable<JQuery> => {
|
Cypress.Commands.add("getComposer", (isRightPanel?: boolean): Chainable<JQuery> => {
|
||||||
const panelClass = isRightPanel ? '.mx_RightPanel' : '.mx_RoomView_body';
|
const panelClass = isRightPanel ? ".mx_RightPanel" : ".mx_RoomView_body";
|
||||||
return cy.get(`${panelClass} .mx_MessageComposer`);
|
return cy.get(`${panelClass} .mx_MessageComposer`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ Cypress.Commands.add("openMessageComposerOptions", (isRightPanel?: boolean): Cha
|
||||||
cy.getComposer(isRightPanel).within(() => {
|
cy.getComposer(isRightPanel).within(() => {
|
||||||
cy.get('[aria-label="More options"]').click();
|
cy.get('[aria-label="More options"]').click();
|
||||||
});
|
});
|
||||||
return cy.get('.mx_MessageComposer_Menu');
|
return cy.get(".mx_MessageComposer_Menu");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
|
|
|
@ -35,10 +35,14 @@ declare global {
|
||||||
|
|
||||||
// Inspired by https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/
|
// Inspired by https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/
|
||||||
Cypress.Commands.add("accessIframe", (selector: string): Chainable<JQuery<HTMLElement>> => {
|
Cypress.Commands.add("accessIframe", (selector: string): Chainable<JQuery<HTMLElement>> => {
|
||||||
return cy.get(selector)
|
return (
|
||||||
.its("0.contentDocument.body").should("not.be.empty")
|
cy
|
||||||
|
.get(selector)
|
||||||
|
.its("0.contentDocument.body")
|
||||||
|
.should("not.be.empty")
|
||||||
// Cypress loses types in the mess of wrapping, so force cast
|
// Cypress loses types in the mess of wrapping, so force cast
|
||||||
.then(cy.wrap) as Chainable<JQuery<HTMLElement>>;
|
.then(cy.wrap) as Chainable<JQuery<HTMLElement>>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
|
|
|
@ -33,9 +33,12 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("enableLabsFeature", (feature: string): Chainable<null> => {
|
Cypress.Commands.add("enableLabsFeature", (feature: string): Chainable<null> => {
|
||||||
return cy.window({ log: false }).then(win => {
|
return cy
|
||||||
|
.window({ log: false })
|
||||||
|
.then((win) => {
|
||||||
win.localStorage.setItem(`mx_labs_feature_${feature}`, "true");
|
win.localStorage.setItem(`mx_labs_feature_${feature}`, "true");
|
||||||
}).then(() => null);
|
})
|
||||||
|
.then(() => null);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
|
|
|
@ -49,19 +49,18 @@ declare global {
|
||||||
* @param username login username
|
* @param username login username
|
||||||
* @param password login password
|
* @param password login password
|
||||||
*/
|
*/
|
||||||
loginUser(
|
loginUser(synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials>;
|
||||||
synapse: SynapseInstance,
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
): Chainable<UserCredentials>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
Cypress.Commands.add("loginUser", (synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials> => {
|
Cypress.Commands.add(
|
||||||
|
"loginUser",
|
||||||
|
(synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials> => {
|
||||||
const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
|
const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
|
||||||
return cy.request<{
|
return cy
|
||||||
|
.request<{
|
||||||
access_token: string;
|
access_token: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
device_id: string;
|
device_id: string;
|
||||||
|
@ -70,14 +69,15 @@ Cypress.Commands.add("loginUser", (synapse: SynapseInstance, username: string, p
|
||||||
url,
|
url,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
"type": "m.login.password",
|
type: "m.login.password",
|
||||||
"identifier": {
|
identifier: {
|
||||||
"type": "m.id.user",
|
type: "m.id.user",
|
||||||
"user": username,
|
user: username,
|
||||||
},
|
},
|
||||||
"password": password,
|
password: password,
|
||||||
},
|
},
|
||||||
}).then(response => ({
|
})
|
||||||
|
.then((response) => ({
|
||||||
password,
|
password,
|
||||||
username,
|
username,
|
||||||
accessToken: response.body.access_token,
|
accessToken: response.body.access_token,
|
||||||
|
@ -85,14 +85,17 @@ Cypress.Commands.add("loginUser", (synapse: SynapseInstance, username: string, p
|
||||||
deviceId: response.body.device_id,
|
deviceId: response.body.device_id,
|
||||||
homeServer: response.body.home_server,
|
homeServer: response.body.home_server,
|
||||||
}));
|
}));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable<UserCredentials> => {
|
Cypress.Commands.add(
|
||||||
|
"initTestUser",
|
||||||
|
(synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable<UserCredentials> => {
|
||||||
// XXX: work around Cypress not clearing IDB between tests
|
// XXX: work around Cypress not clearing IDB between tests
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
win.indexedDB.databases()?.then(databases => {
|
win.indexedDB.databases()?.then((databases) => {
|
||||||
databases.forEach(database => {
|
databases.forEach((database) => {
|
||||||
win.indexedDB.deleteDatabase(database.name);
|
win.indexedDB.deleteDatabase(database.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -100,11 +103,14 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
|
||||||
|
|
||||||
const username = Cypress._.uniqueId("userId_");
|
const username = Cypress._.uniqueId("userId_");
|
||||||
const password = Cypress._.uniqueId("password_");
|
const password = Cypress._.uniqueId("password_");
|
||||||
return cy.registerUser(synapse, username, password, displayName).then(() => {
|
return cy
|
||||||
|
.registerUser(synapse, username, password, displayName)
|
||||||
|
.then(() => {
|
||||||
return cy.loginUser(synapse, username, password);
|
return cy.loginUser(synapse, username, password);
|
||||||
}).then(response => {
|
})
|
||||||
|
.then((response) => {
|
||||||
cy.log(`Registered test user ${username} with displayname ${displayName}`);
|
cy.log(`Registered test user ${username} with displayname ${displayName}`);
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
// Seed the localStorage with the required credentials
|
// Seed the localStorage with the required credentials
|
||||||
win.localStorage.setItem("mx_hs_url", synapse.baseUrl);
|
win.localStorage.setItem("mx_hs_url", synapse.baseUrl);
|
||||||
win.localStorage.setItem("mx_user_id", response.userId);
|
win.localStorage.setItem("mx_user_id", response.userId);
|
||||||
|
@ -120,10 +126,13 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
|
||||||
|
|
||||||
prelaunchFn?.();
|
prelaunchFn?.();
|
||||||
|
|
||||||
return cy.visit("/").then(() => {
|
return cy
|
||||||
|
.visit("/")
|
||||||
|
.then(() => {
|
||||||
// wait for the app to load
|
// wait for the app to load
|
||||||
return cy.get(".mx_MatrixChat", { timeout: 30000 });
|
return cy.get(".mx_MatrixChat", { timeout: 30000 });
|
||||||
}).then(() => ({
|
})
|
||||||
|
.then(() => ({
|
||||||
password,
|
password,
|
||||||
username,
|
username,
|
||||||
accessToken: response.accessToken,
|
accessToken: response.accessToken,
|
||||||
|
@ -132,4 +141,5 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
|
||||||
homeServer: response.homeServer,
|
homeServer: response.homeServer,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -35,27 +35,35 @@ declare global {
|
||||||
|
|
||||||
Cypress.Commands.add("goOffline", (): void => {
|
Cypress.Commands.add("goOffline", (): void => {
|
||||||
cy.log("Going offline");
|
cy.log("Going offline");
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
cy.intercept("**/_matrix/**", {
|
cy.intercept(
|
||||||
|
"**/_matrix/**",
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
Authorization: "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
||||||
},
|
},
|
||||||
}, req => {
|
},
|
||||||
|
(req) => {
|
||||||
req.destroy();
|
req.destroy();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("goOnline", (): void => {
|
Cypress.Commands.add("goOnline", (): void => {
|
||||||
cy.log("Going online");
|
cy.log("Going online");
|
||||||
cy.window({ log: false }).then(win => {
|
cy.window({ log: false }).then((win) => {
|
||||||
cy.intercept("**/_matrix/**", {
|
cy.intercept(
|
||||||
|
"**/_matrix/**",
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
Authorization: "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
||||||
},
|
},
|
||||||
}, req => {
|
},
|
||||||
|
(req) => {
|
||||||
req.continue();
|
req.continue();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
win.dispatchEvent(new Event("online"));
|
win.dispatchEvent(new Event("online"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
import { SnapshotOptions as PercySnapshotOptions } from '@percy/core';
|
import { SnapshotOptions as PercySnapshotOptions } from "@percy/core";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
@ -39,14 +39,14 @@ declare global {
|
||||||
|
|
||||||
Cypress.Commands.add("percySnapshotElement", { prevSubject: "element" }, (subject, name, options) => {
|
Cypress.Commands.add("percySnapshotElement", { prevSubject: "element" }, (subject, name, options) => {
|
||||||
cy.percySnapshot(name, {
|
cy.percySnapshot(name, {
|
||||||
domTransformation: documentClone => scope(documentClone, subject.selector),
|
domTransformation: (documentClone) => scope(documentClone, subject.selector),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function scope(documentClone: Document, selector: string): Document {
|
function scope(documentClone: Document, selector: string): Document {
|
||||||
const element = documentClone.querySelector(selector);
|
const element = documentClone.querySelector(selector);
|
||||||
documentClone.querySelector('body').innerHTML = element.outerHTML;
|
documentClone.querySelector("body").innerHTML = element.outerHTML;
|
||||||
|
|
||||||
return documentClone;
|
return documentClone;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
|
|
||||||
import Chainable = Cypress.Chainable;
|
import Chainable = Cypress.Chainable;
|
||||||
import AUTWindow = Cypress.AUTWindow;
|
import AUTWindow = Cypress.AUTWindow;
|
||||||
import { ProxyInstance } from '../plugins/sliding-sync';
|
import { ProxyInstance } from "../plugins/sliding-sync";
|
||||||
import { SynapseInstance } from "../plugins/synapsedocker";
|
import { SynapseInstance } from "../plugins/synapsedocker";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -49,7 +49,7 @@ function stopProxy(proxy?: ProxyInstance): Chainable<AUTWindow> {
|
||||||
if (!proxy) return;
|
if (!proxy) return;
|
||||||
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
|
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
|
||||||
return cy.window({ log: false }).then((win) => {
|
return cy.window({ log: false }).then((win) => {
|
||||||
win.location.href = 'about:blank';
|
win.location.href = "about:blank";
|
||||||
cy.task("proxyStop", proxy);
|
cy.task("proxyStop", proxy);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,26 +102,27 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("getSettingsStore", (): Chainable<typeof SettingsStore> => {
|
Cypress.Commands.add("getSettingsStore", (): Chainable<typeof SettingsStore> => {
|
||||||
return cy.window({ log: false }).then(win => win.mxSettingsStore);
|
return cy.window({ log: false }).then((win) => win.mxSettingsStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("setSettingValue", (
|
Cypress.Commands.add(
|
||||||
name: string,
|
"setSettingValue",
|
||||||
roomId: string,
|
(name: string, roomId: string, level: SettingLevel, value: any): Chainable<void> => {
|
||||||
level: SettingLevel,
|
|
||||||
value: any,
|
|
||||||
): Chainable<void> => {
|
|
||||||
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
||||||
return cy.wrap(store.setValue(name, roomId, level, value));
|
return cy.wrap(store.setValue(name, roomId, level, value));
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
Cypress.Commands.add("getSettingValue", <T = any>(name: string, roomId?: string, excludeDefault?: boolean): Chainable<T> => {
|
Cypress.Commands.add(
|
||||||
|
"getSettingValue",
|
||||||
|
<T = any>(name: string, roomId?: string, excludeDefault?: boolean): Chainable<T> => {
|
||||||
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
||||||
return store.getValue(name, roomId, excludeDefault);
|
return store.getValue(name, roomId, excludeDefault);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Cypress.Commands.add("openUserMenu", (): Chainable<JQuery<HTMLElement>> => {
|
Cypress.Commands.add("openUserMenu", (): Chainable<JQuery<HTMLElement>> => {
|
||||||
cy.get('[aria-label="User menu"]').click();
|
cy.get('[aria-label="User menu"]').click();
|
||||||
|
@ -162,13 +163,19 @@ Cypress.Commands.add("closeDialog", (): Chainable<JQuery<HTMLElement>> => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("joinBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
Cypress.Commands.add("joinBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
||||||
return cy.contains(".mx_BetaCard_title", name).closest(".mx_BetaCard").within(() => {
|
return cy
|
||||||
|
.contains(".mx_BetaCard_title", name)
|
||||||
|
.closest(".mx_BetaCard")
|
||||||
|
.within(() => {
|
||||||
return cy.get(".mx_BetaCard_buttons").contains("Join the beta").click();
|
return cy.get(".mx_BetaCard_buttons").contains("Join the beta").click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("leaveBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
Cypress.Commands.add("leaveBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
||||||
return cy.contains(".mx_BetaCard_title", name).closest(".mx_BetaCard").within(() => {
|
return cy
|
||||||
|
.contains(".mx_BetaCard_title", name)
|
||||||
|
.closest(".mx_BetaCard")
|
||||||
|
.within(() => {
|
||||||
return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click();
|
return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
import Chainable = Cypress.Chainable;
|
import Chainable = Cypress.Chainable;
|
||||||
import AUTWindow = Cypress.AUTWindow;
|
import AUTWindow = Cypress.AUTWindow;
|
||||||
|
@ -64,7 +64,7 @@ function stopSynapse(synapse?: SynapseInstance): Chainable<AUTWindow> {
|
||||||
if (!synapse) return;
|
if (!synapse) return;
|
||||||
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
|
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
|
||||||
return cy.window({ log: false }).then((win) => {
|
return cy.window({ log: false }).then((win) => {
|
||||||
win.location.href = 'about:blank';
|
win.location.href = "about:blank";
|
||||||
cy.task("synapseStop", synapse.synapseId);
|
cy.task("synapseStop", synapse.synapseId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -83,14 +83,17 @@ function registerUser(
|
||||||
displayName?: string,
|
displayName?: string,
|
||||||
): Chainable<Credentials> {
|
): Chainable<Credentials> {
|
||||||
const url = `${synapse.baseUrl}/_synapse/admin/v1/register`;
|
const url = `${synapse.baseUrl}/_synapse/admin/v1/register`;
|
||||||
return cy.then(() => {
|
return cy
|
||||||
|
.then(() => {
|
||||||
// get a nonce
|
// get a nonce
|
||||||
return cy.request<{ nonce: string }>({ url });
|
return cy.request<{ nonce: string }>({ url });
|
||||||
}).then(response => {
|
})
|
||||||
|
.then((response) => {
|
||||||
const { nonce } = response.body;
|
const { nonce } = response.body;
|
||||||
const mac = crypto.createHmac('sha1', synapse.registrationSecret).update(
|
const mac = crypto
|
||||||
`${nonce}\0${username}\0${password}\0notadmin`,
|
.createHmac("sha1", synapse.registrationSecret)
|
||||||
).digest('hex');
|
.update(`${nonce}\0${username}\0${password}\0notadmin`)
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
return cy.request<{
|
return cy.request<{
|
||||||
access_token: string;
|
access_token: string;
|
||||||
|
@ -109,7 +112,8 @@ function registerUser(
|
||||||
displayname: displayName,
|
displayname: displayName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}).then(response => ({
|
})
|
||||||
|
.then((response) => ({
|
||||||
homeServer: response.body.home_server,
|
homeServer: response.body.home_server,
|
||||||
accessToken: response.body.access_token,
|
accessToken: response.body.access_token,
|
||||||
userId: response.body.user_id,
|
userId: response.body.user_id,
|
||||||
|
|
|
@ -38,7 +38,9 @@ export interface Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("scrollToTop", (): void => {
|
Cypress.Commands.add("scrollToTop", (): void => {
|
||||||
cy.get(".mx_RoomView_timeline .mx_ScrollPanel").scrollTo("top", { duration: 100 }).then(ref => {
|
cy.get(".mx_RoomView_timeline .mx_ScrollPanel")
|
||||||
|
.scrollTo("top", { duration: 100 })
|
||||||
|
.then((ref) => {
|
||||||
if (ref.scrollTop() > 0) {
|
if (ref.scrollTop() > 0) {
|
||||||
return cy.scrollToTop();
|
return cy.scrollToTop();
|
||||||
}
|
}
|
||||||
|
@ -48,7 +50,7 @@ Cypress.Commands.add("scrollToTop", (): void => {
|
||||||
Cypress.Commands.add("findEventTile", (sender: string, body: string): Chainable<JQuery> => {
|
Cypress.Commands.add("findEventTile", (sender: string, body: string): Chainable<JQuery> => {
|
||||||
// We can't just use a bunch of `.contains` here due to continuations meaning that the events don't
|
// We can't just use a bunch of `.contains` here due to continuations meaning that the events don't
|
||||||
// have their own rendered sender displayname so we have to walk the list to keep track of the sender.
|
// have their own rendered sender displayname so we have to walk the list to keep track of the sender.
|
||||||
return cy.get(".mx_RoomView_MessageList .mx_EventTile").then(refs => {
|
return cy.get(".mx_RoomView_MessageList .mx_EventTile").then((refs) => {
|
||||||
let latestSender: string;
|
let latestSender: string;
|
||||||
for (let i = 0; i < refs.length; i++) {
|
for (let i = 0; i < refs.length; i++) {
|
||||||
const ref = refs.eq(i);
|
const ref = refs.eq(i);
|
||||||
|
|
|
@ -29,7 +29,7 @@ declare global {
|
||||||
|
|
||||||
interface cy {
|
interface cy {
|
||||||
all<T extends Cypress.Chainable[] | []>(
|
all<T extends Cypress.Chainable[] | []>(
|
||||||
commands: T
|
commands: T,
|
||||||
): Cypress.Chainable<{ [P in keyof T]: ChainableValue<T[P]> }>;
|
): Cypress.Chainable<{ [P in keyof T]: ChainableValue<T[P]> }>;
|
||||||
queue: any;
|
queue: any;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ cy.all = function all(commands): Cypress.Chainable {
|
||||||
return cy.wrap(
|
return cy.wrap(
|
||||||
// @see https://lodash.com/docs/4.17.15#lodash
|
// @see https://lodash.com/docs/4.17.15#lodash
|
||||||
Cypress._(commands)
|
Cypress._(commands)
|
||||||
.map(cmd => {
|
.map((cmd) => {
|
||||||
return cmd[chainStart]
|
return cmd[chainStart]
|
||||||
? cmd[chainStart].attributes
|
? cmd[chainStart].attributes
|
||||||
: Cypress._.find(cy.queue.get(), {
|
: Cypress._.find(cy.queue.get(), {
|
||||||
|
@ -68,7 +68,7 @@ cy.all = function all(commands): Cypress.Chainable {
|
||||||
})
|
})
|
||||||
.concat(stopCommand.attributes)
|
.concat(stopCommand.attributes)
|
||||||
.slice(1)
|
.slice(1)
|
||||||
.map(cmd => {
|
.map((cmd) => {
|
||||||
return cmd.prev.get("subject");
|
return cmd.prev.get("subject");
|
||||||
})
|
})
|
||||||
.value(),
|
.value(),
|
||||||
|
|
|
@ -2,22 +2,12 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2016",
|
"target": "es2016",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"lib": [
|
"lib": ["es2020", "dom", "dom.iterable"],
|
||||||
"es2020",
|
"types": ["cypress", "cypress-axe", "@percy/cypress"],
|
||||||
"dom",
|
|
||||||
"dom.iterable"
|
|
||||||
],
|
|
||||||
"types": [
|
|
||||||
"cypress",
|
|
||||||
"cypress-axe",
|
|
||||||
"@percy/cypress"
|
|
||||||
],
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"module": "commonjs"
|
"module": "commonjs"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["**/*.ts"]
|
||||||
"**/*.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,8 @@ the content string, caret nodes need to be ignored, as they would confuse the mo
|
||||||
|
|
||||||
As part of the reconciliation, the caret position is also adjusted to any changes
|
As part of the reconciliation, the caret position is also adjusted to any changes
|
||||||
the model made to the input. The caret is passed around in two formats.
|
the model made to the input. The caret is passed around in two formats.
|
||||||
The model receives the caret *offset* within the content string (which includes
|
The model receives the caret _offset_ within the content string (which includes
|
||||||
an atNodeEnd flag to make it unambiguous if it is at a part and or the next part start).
|
an atNodeEnd flag to make it unambiguous if it is at a part and or the next part start).
|
||||||
The model converts this to a caret *position* internally, which has a partIndex
|
The model converts this to a caret _position_ internally, which has a partIndex
|
||||||
and an offset within the part text, which is more natural to work with.
|
and an offset within the part text, which is more natural to work with.
|
||||||
From there on, the caret *position* is used, also during reconciliation.
|
From there on, the caret _position_ is used, also during reconciliation.
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
# Cypress in Element Web
|
# Cypress in Element Web
|
||||||
|
|
||||||
## Scope of this Document
|
## Scope of this Document
|
||||||
|
|
||||||
This doc is about our Cypress tests in Element Web and how we use Cypress to write tests.
|
This doc is about our Cypress tests in Element Web and how we use Cypress to write tests.
|
||||||
It aims to cover:
|
It aims to cover:
|
||||||
* How to run the tests yourself
|
|
||||||
* How the tests work
|
- How to run the tests yourself
|
||||||
* How to write great Cypress tests
|
- How the tests work
|
||||||
* Visual testing
|
- How to write great Cypress tests
|
||||||
|
- Visual testing
|
||||||
|
|
||||||
## Running the Tests
|
## Running the Tests
|
||||||
|
|
||||||
Our Cypress tests run automatically as part of our CI along with our other tests,
|
Our Cypress tests run automatically as part of our CI along with our other tests,
|
||||||
on every pull request and on every merge to develop & master.
|
on every pull request and on every merge to develop & master.
|
||||||
|
|
||||||
|
@ -43,6 +46,7 @@ yarn run test:cypress:open
|
||||||
```
|
```
|
||||||
|
|
||||||
## How the Tests Work
|
## How the Tests Work
|
||||||
|
|
||||||
Everything Cypress-related lives in the `cypress/` subdirectory of react-sdk
|
Everything Cypress-related lives in the `cypress/` subdirectory of react-sdk
|
||||||
as is typical for Cypress tests. Likewise, tests live in `cypress/e2e`.
|
as is typical for Cypress tests. Likewise, tests live in `cypress/e2e`.
|
||||||
|
|
||||||
|
@ -68,6 +72,7 @@ with each instance in a separate directory named after its ID. These logs are re
|
||||||
at the start of each test run.
|
at the start of each test run.
|
||||||
|
|
||||||
## Writing Tests
|
## Writing Tests
|
||||||
|
|
||||||
Mostly this is the same advice as for writing any other Cypress test: the Cypress
|
Mostly this is the same advice as for writing any other Cypress test: the Cypress
|
||||||
docs are well worth a read if you're not already familiar with Cypress testing, eg.
|
docs are well worth a read if you're not already familiar with Cypress testing, eg.
|
||||||
https://docs.cypress.io/guides/references/best-practices. To avoid your tests being
|
https://docs.cypress.io/guides/references/best-practices. To avoid your tests being
|
||||||
|
@ -75,11 +80,12 @@ flaky it is also recommended to give https://docs.cypress.io/guides/core-concept
|
||||||
a read.
|
a read.
|
||||||
|
|
||||||
### Getting a Synapse
|
### Getting a Synapse
|
||||||
|
|
||||||
The key difference is in starting Synapse instances. Tests use this plugin via
|
The key difference is in starting Synapse instances. Tests use this plugin via
|
||||||
`cy.startSynapse()` to provide a Synapse instance to log into:
|
`cy.startSynapse()` to provide a Synapse instance to log into:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
cy.startSynapse("consent").then(result => {
|
cy.startSynapse("consent").then((result) => {
|
||||||
synapse = result;
|
synapse = result;
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
@ -96,21 +102,24 @@ Synapse instance for each test suite, i.e. in `before()`, and then tear it down
|
||||||
|
|
||||||
To later destroy your Synapse you should call `stopSynapse`, passing the SynapseInstance
|
To later destroy your Synapse you should call `stopSynapse`, passing the SynapseInstance
|
||||||
object you received when starting it.
|
object you received when starting it.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
cy.stopSynapse(synapse);
|
cy.stopSynapse(synapse);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Synapse Config Templates
|
### Synapse Config Templates
|
||||||
|
|
||||||
When a Synapse instance is started, it's given a config generated from one of the config
|
When a Synapse instance is started, it's given a config generated from one of the config
|
||||||
templates in `cypress/plugins/synapsedocker/templates`. There are a couple of special files
|
templates in `cypress/plugins/synapsedocker/templates`. There are a couple of special files
|
||||||
in these templates:
|
in these templates:
|
||||||
* `homeserver.yaml`:
|
|
||||||
|
- `homeserver.yaml`:
|
||||||
Template substitution happens in this file. Template variables are:
|
Template substitution happens in this file. Template variables are:
|
||||||
* `REGISTRATION_SECRET`: The secret used to register users via the REST API.
|
- `REGISTRATION_SECRET`: The secret used to register users via the REST API.
|
||||||
* `MACAROON_SECRET_KEY`: Generated each time for security
|
- `MACAROON_SECRET_KEY`: Generated each time for security
|
||||||
* `FORM_SECRET`: Generated each time for security
|
- `FORM_SECRET`: Generated each time for security
|
||||||
* `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at
|
- `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at
|
||||||
* `localhost.signing.key`: A signing key is auto-generated and saved to this file.
|
- `localhost.signing.key`: A signing key is auto-generated and saved to this file.
|
||||||
Config templates should not contain a signing key and instead assume that one will exist
|
Config templates should not contain a signing key and instead assume that one will exist
|
||||||
in this file.
|
in this file.
|
||||||
|
|
||||||
|
@ -118,10 +127,13 @@ All other files in the template are copied recursively to `/data/`, so the file
|
||||||
in a template can be referenced in the config as `/data/foo.html`.
|
in a template can be referenced in the config as `/data/foo.html`.
|
||||||
|
|
||||||
### Logging In
|
### Logging In
|
||||||
|
|
||||||
There exists a basic utility to start the app with a random user already logged in:
|
There exists a basic utility to start the app with a random user already logged in:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
cy.initTestUser(synapse, "Jeff");
|
cy.initTestUser(synapse, "Jeff");
|
||||||
```
|
```
|
||||||
|
|
||||||
It takes the SynapseInstance you received from `startSynapse` and a display name for your test user.
|
It takes the SynapseInstance you received from `startSynapse` and a display name for your test user.
|
||||||
This custom command will register a random userId using the registrationSecret with a random password
|
This custom command will register a random userId using the registrationSecret with a random password
|
||||||
and the given display name. The returned Chainable will contain details about the credentials for if
|
and the given display name. The returned Chainable will contain details about the credentials for if
|
||||||
|
@ -132,20 +144,24 @@ The internals of how this custom command run may be swapped out later,
|
||||||
but the signature can be maintained for simpler maintenance.
|
but the signature can be maintained for simpler maintenance.
|
||||||
|
|
||||||
### Joining a Room
|
### Joining a Room
|
||||||
|
|
||||||
Many tests will also want to start with the client in a room, ready to send & receive messages. Best
|
Many tests will also want to start with the client in a room, ready to send & receive messages. Best
|
||||||
way to do this may be to get an access token for the user and use this to create a room with the REST
|
way to do this may be to get an access token for the user and use this to create a room with the REST
|
||||||
API before logging the user in. You can make use of `cy.getBot(synapse)` and `cy.getClient()` to do this.
|
API before logging the user in. You can make use of `cy.getBot(synapse)` and `cy.getClient()` to do this.
|
||||||
|
|
||||||
### Convenience APIs
|
### Convenience APIs
|
||||||
|
|
||||||
We should probably end up with convenience APIs that wrap the synapse creation, logging in and room
|
We should probably end up with convenience APIs that wrap the synapse creation, logging in and room
|
||||||
creation that can be called to set up tests.
|
creation that can be called to set up tests.
|
||||||
|
|
||||||
### Using matrix-js-sdk
|
### Using matrix-js-sdk
|
||||||
|
|
||||||
Due to the way we run the Cypress tests in CI, at this time you can only use the matrix-js-sdk module
|
Due to the way we run the Cypress tests in CI, at this time you can only use the matrix-js-sdk module
|
||||||
exposed on `window.matrixcs`. This has the limitation that it is only accessible with the app loaded.
|
exposed on `window.matrixcs`. This has the limitation that it is only accessible with the app loaded.
|
||||||
This may be revisited in the future.
|
This may be revisited in the future.
|
||||||
|
|
||||||
## Good Test Hygiene
|
## Good Test Hygiene
|
||||||
|
|
||||||
This section mostly summarises general good Cypress testing practice, and should not be news to anyone
|
This section mostly summarises general good Cypress testing practice, and should not be news to anyone
|
||||||
already familiar with Cypress.
|
already familiar with Cypress.
|
||||||
|
|
||||||
|
@ -158,11 +174,11 @@ already familiar with Cypress.
|
||||||
1. Avoid explicit waits. `cy.get()` will implicitly wait for the specified element to appear and
|
1. Avoid explicit waits. `cy.get()` will implicitly wait for the specified element to appear and
|
||||||
all assertions are retired until they either pass or time out, so you should never need to
|
all assertions are retired until they either pass or time out, so you should never need to
|
||||||
manually wait for an element.
|
manually wait for an element.
|
||||||
* For example, for asserting about editing an already-edited message, you can't wait for the
|
- For example, for asserting about editing an already-edited message, you can't wait for the
|
||||||
'edited' element to appear as there was already one there, but you can assert that the body
|
'edited' element to appear as there was already one there, but you can assert that the body
|
||||||
of the message is what is should be after the second edit and this assertion will pass once
|
of the message is what is should be after the second edit and this assertion will pass once
|
||||||
it becomes true. You can then assert that the 'edited' element is still in the DOM.
|
it becomes true. You can then assert that the 'edited' element is still in the DOM.
|
||||||
* You can also wait for other things like network requests in the
|
- You can also wait for other things like network requests in the
|
||||||
browser to complete (https://docs.cypress.io/guides/guides/network-requests#Waiting).
|
browser to complete (https://docs.cypress.io/guides/guides/network-requests#Waiting).
|
||||||
Needing to wait for things can also be because of race conditions in the app itself, which ideally
|
Needing to wait for things can also be because of race conditions in the app itself, which ideally
|
||||||
shouldn't be there!
|
shouldn't be there!
|
||||||
|
@ -171,6 +187,7 @@ This is a small selection - the Cypress best practices guide, linked above, has
|
||||||
should generally try to adhere to them.
|
should generally try to adhere to them.
|
||||||
|
|
||||||
## Percy Visual Testing
|
## Percy Visual Testing
|
||||||
|
|
||||||
We also support visual testing via Percy, this extracts the DOM from Cypress and renders it using custom renderers
|
We also support visual testing via Percy, this extracts the DOM from Cypress and renders it using custom renderers
|
||||||
for Safari, Firefox, Chrome & Edge, allowing us to spot visual regressions before they become release regressions.
|
for Safari, Firefox, Chrome & Edge, allowing us to spot visual regressions before they become release regressions.
|
||||||
Each `cy.percySnapshot()` call results in 8 screenshots (4 browsers, 2 sizes) this can quickly be exhausted and
|
Each `cy.percySnapshot()` call results in 8 screenshots (4 browsers, 2 sizes) this can quickly be exhausted and
|
||||||
|
@ -178,4 +195,3 @@ so we only run Percy testing on `develop` and PRs which are labelled `X-Needs-Pe
|
||||||
|
|
||||||
To record a snapshot use `cy.percySnapshot()`, you may have to pass `percyCSS` into the 2nd argument to hide certain
|
To record a snapshot use `cy.percySnapshot()`, you may have to pass `percyCSS` into the 2nd argument to hide certain
|
||||||
elements which contain dynamic/generated data to avoid them cause false positives in the Percy screenshot diffs.
|
elements which contain dynamic/generated data to avoid them cause false positives in the Percy screenshot diffs.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Composer Features
|
# Composer Features
|
||||||
|
|
||||||
## Auto Complete
|
## Auto Complete
|
||||||
|
|
||||||
- Hitting tab tries to auto-complete the word before the caret as a room member
|
- Hitting tab tries to auto-complete the word before the caret as a room member
|
||||||
|
|
|
@ -17,7 +17,7 @@ Let's say we want to close a menu when the correct keys were pressed:
|
||||||
```ts
|
```ts
|
||||||
const onKeyDown = (ev: KeyboardEvent): void => {
|
const onKeyDown = (ev: KeyboardEvent): void => {
|
||||||
let handled = true;
|
let handled = true;
|
||||||
const action = getKeyBindingManager().getAccessibilityAction(ev)
|
const action = getKeyBindingManager().getAccessibilityAction(ev);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case KeyBindingAction.Escape:
|
case KeyBindingAction.Escape:
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
@ -31,7 +31,7 @@ const onKeyDown = (ev: KeyboardEvent): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Managing keyboard shortcuts
|
## Managing keyboard shortcuts
|
||||||
|
|
|
@ -6,6 +6,7 @@ Each .svg exports a `ReactComponent` at the named export `Icon`.
|
||||||
Icons have `role="presentation"` and `aria-hidden` automatically applied. These can be overriden by passing props to the icon component.
|
Icons have `role="presentation"` and `aria-hidden` automatically applied. These can be overriden by passing props to the icon component.
|
||||||
|
|
||||||
eg
|
eg
|
||||||
|
|
||||||
```
|
```
|
||||||
import { Icon as FavoriteIcon } from 'res/img/element-icons/favorite.svg';
|
import { Icon as FavoriteIcon } from 'res/img/element-icons/favorite.svg';
|
||||||
|
|
||||||
|
|
|
@ -6,22 +6,24 @@ instructions on setting up Jitsi.
|
||||||
The react-sdk wraps all Jitsi call widgets in a local wrapper called `jitsi.html`
|
The react-sdk wraps all Jitsi call widgets in a local wrapper called `jitsi.html`
|
||||||
which takes several parameters:
|
which takes several parameters:
|
||||||
|
|
||||||
*Query string*:
|
_Query string_:
|
||||||
* `widgetId`: The ID of the widget. This is needed for communication back to the
|
|
||||||
|
- `widgetId`: The ID of the widget. This is needed for communication back to the
|
||||||
react-sdk.
|
react-sdk.
|
||||||
* `parentUrl`: The URL of the parent window. This is also needed for
|
- `parentUrl`: The URL of the parent window. This is also needed for
|
||||||
communication back to the react-sdk.
|
communication back to the react-sdk.
|
||||||
|
|
||||||
*Hash/fragment (formatted as a query string)*:
|
_Hash/fragment (formatted as a query string)_:
|
||||||
* `conferenceDomain`: The domain to connect Jitsi Meet to.
|
|
||||||
* `conferenceId`: The room or conference ID to connect Jitsi Meet to.
|
- `conferenceDomain`: The domain to connect Jitsi Meet to.
|
||||||
* `isAudioOnly`: Boolean for whether this is a voice-only conference. May not
|
- `conferenceId`: The room or conference ID to connect Jitsi Meet to.
|
||||||
|
- `isAudioOnly`: Boolean for whether this is a voice-only conference. May not
|
||||||
be present, should default to `false`.
|
be present, should default to `false`.
|
||||||
* `displayName`: The display name of the user viewing the widget. May not
|
- `displayName`: The display name of the user viewing the widget. May not
|
||||||
be present or could be null.
|
be present or could be null.
|
||||||
* `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May
|
- `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May
|
||||||
not be present or could be null.
|
not be present or could be null.
|
||||||
* `userId`: The MXID of the user viewing the widget. May not be present or could
|
- `userId`: The MXID of the user viewing the widget. May not be present or could
|
||||||
be null.
|
be null.
|
||||||
|
|
||||||
The react-sdk will assume that `jitsi.html` is at the path of wherever it is currently
|
The react-sdk will assume that `jitsi.html` is at the path of wherever it is currently
|
||||||
|
|
|
@ -36,4 +36,3 @@ mechanisms.
|
||||||
|
|
||||||
The `EchoStore` is responsible for ensuring that the appropriate non-urgent toast (lower left)
|
The `EchoStore` is responsible for ensuring that the appropriate non-urgent toast (lower left)
|
||||||
is set up, where the dialog then drives through the contexts and transactions.
|
is set up, where the dialog then drives through the contexts and transactions.
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ It's so complicated it needs its own README.
|
||||||
![](img/RoomListStore2.png)
|
![](img/RoomListStore2.png)
|
||||||
|
|
||||||
Legend:
|
Legend:
|
||||||
* Orange = External event.
|
|
||||||
* Purple = Deterministic flow.
|
- Orange = External event.
|
||||||
* Green = Algorithm definition.
|
- Purple = Deterministic flow.
|
||||||
* Red = Exit condition/point.
|
- Green = Algorithm definition.
|
||||||
* Blue = Process definition.
|
- Red = Exit condition/point.
|
||||||
|
- Blue = Process definition.
|
||||||
|
|
||||||
## Algorithms involved
|
## Algorithms involved
|
||||||
|
|
||||||
|
@ -22,7 +23,6 @@ Behaviour of the overall room list (sticky rooms, etc) are determined by the gen
|
||||||
class. Here is where much of the coordination from the room list store is done to figure out which list
|
class. Here is where much of the coordination from the room list store is done to figure out which list
|
||||||
algorithm to call, instead of having all the logic in the room list store itself.
|
algorithm to call, instead of having all the logic in the room list store itself.
|
||||||
|
|
||||||
|
|
||||||
Tag sorting is effectively the comparator supplied to the list algorithm. This gives the list algorithm
|
Tag sorting is effectively the comparator supplied to the list algorithm. This gives the list algorithm
|
||||||
the power to decide when and how to apply the tag sorting, if at all. For example, the importance algorithm,
|
the power to decide when and how to apply the tag sorting, if at all. For example, the importance algorithm,
|
||||||
later described in this document, heavily uses the list ordering behaviour to break the tag into categories.
|
later described in this document, heavily uses the list ordering behaviour to break the tag into categories.
|
||||||
|
@ -68,13 +68,13 @@ simply get the manual sorting algorithm applied to them with no further involvem
|
||||||
algorithm. There are 4 categories: Red, Grey, Bold, and Idle. Each has their own definition based off
|
algorithm. There are 4 categories: Red, Grey, Bold, and Idle. Each has their own definition based off
|
||||||
relative (perceived) importance to the user:
|
relative (perceived) importance to the user:
|
||||||
|
|
||||||
* **Red**: The room has unread mentions waiting for the user.
|
- **Red**: The room has unread mentions waiting for the user.
|
||||||
* **Grey**: The room has unread notifications waiting for the user. Notifications are simply unread
|
- **Grey**: The room has unread notifications waiting for the user. Notifications are simply unread
|
||||||
messages which cause a push notification or badge count. Typically, this is the default as rooms get
|
messages which cause a push notification or badge count. Typically, this is the default as rooms get
|
||||||
set to 'All Messages'.
|
set to 'All Messages'.
|
||||||
* **Bold**: The room has unread messages waiting for the user. Essentially this is a grey room without
|
- **Bold**: The room has unread messages waiting for the user. Essentially this is a grey room without
|
||||||
a badge/notification count (or 'Mentions Only'/'Muted').
|
a badge/notification count (or 'Mentions Only'/'Muted').
|
||||||
* **Idle**: No useful (see definition of useful above) activity has occurred in the room since the user
|
- **Idle**: No useful (see definition of useful above) activity has occurred in the room since the user
|
||||||
last read it.
|
last read it.
|
||||||
|
|
||||||
Conveniently, each tag gets ordered by those categories as presented: red rooms appear above grey, grey
|
Conveniently, each tag gets ordered by those categories as presented: red rooms appear above grey, grey
|
||||||
|
|
|
@ -8,7 +8,6 @@ During an onscroll event, we check whether we're getting close to the top or bot
|
||||||
|
|
||||||
ScrollPanel supports a mode to prevent it shrinking. This is used to prevent a jump when at the bottom of the timeline and people start and stop typing. It gets cleared automatically when 200px above the bottom of the timeline.
|
ScrollPanel supports a mode to prevent it shrinking. This is used to prevent a jump when at the bottom of the timeline and people start and stop typing. It gets cleared automatically when 200px above the bottom of the timeline.
|
||||||
|
|
||||||
|
|
||||||
## BACAT (Bottom-Aligned, Clipped-At-Top) scrolling
|
## BACAT (Bottom-Aligned, Clipped-At-Top) scrolling
|
||||||
|
|
||||||
BACAT scrolling implements a different way of restoring the scroll position in the timeline while tiles out of view are changing height or tiles are being added or removed. It was added in https://github.com/matrix-org/matrix-react-sdk/pull/2842.
|
BACAT scrolling implements a different way of restoring the scroll position in the timeline while tiles out of view are changing height or tiles are being added or removed. It was added in https://github.com/matrix-org/matrix-react-sdk/pull/2842.
|
||||||
|
|
|
@ -5,23 +5,22 @@ different values for a setting at particular levels of interest. For example, a
|
||||||
they want URL previews off, but in all other rooms they want them enabled. The `SettingsStore` helps mask the complexity
|
they want URL previews off, but in all other rooms they want them enabled. The `SettingsStore` helps mask the complexity
|
||||||
of dealing with the different levels and exposes easy to use getters and setters.
|
of dealing with the different levels and exposes easy to use getters and setters.
|
||||||
|
|
||||||
|
|
||||||
## Levels
|
## Levels
|
||||||
|
|
||||||
Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in
|
Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in
|
||||||
order of priority, are:
|
order of priority, are:
|
||||||
* `device` - The current user's device
|
|
||||||
* `room-device` - The current user's device, but only when in a specific room
|
- `device` - The current user's device
|
||||||
* `room-account` - The current user's account, but only when in a specific room
|
- `room-device` - The current user's device, but only when in a specific room
|
||||||
* `account` - The current user's account
|
- `room-account` - The current user's account, but only when in a specific room
|
||||||
* `room` - A specific room (setting for all members of the room)
|
- `account` - The current user's account
|
||||||
* `config` - Values are defined by the `setting_defaults` key (usually) in `config.json`
|
- `room` - A specific room (setting for all members of the room)
|
||||||
* `default` - The hardcoded default for the settings
|
- `config` - Values are defined by the `setting_defaults` key (usually) in `config.json`
|
||||||
|
- `default` - The hardcoded default for the settings
|
||||||
|
|
||||||
Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure
|
Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure
|
||||||
that room administrators cannot force account-only settings upon participants.
|
that room administrators cannot force account-only settings upon participants.
|
||||||
|
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
Settings are the different options a user may set or experience in the application. These are pre-defined in
|
Settings are the different options a user may set or experience in the application. These are pre-defined in
|
||||||
|
@ -29,6 +28,7 @@ Settings are the different options a user may set or experience in the applicati
|
||||||
|
|
||||||
Settings that support the config level can be set in the config file under the `setting_defaults` key (note that some
|
Settings that support the config level can be set in the config file under the `setting_defaults` key (note that some
|
||||||
settings, like the "theme" setting, are special cased in the config file):
|
settings, like the "theme" setting, are special cased in the config file):
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
|
@ -56,6 +56,7 @@ target level.
|
||||||
Values are defined at particular levels and should be done in a safe manner. There are two checks to perform to ensure a
|
Values are defined at particular levels and should be done in a safe manner. There are two checks to perform to ensure a
|
||||||
clean save: is the level supported and can the user actually set the value. In most cases, neither should be an issue
|
clean save: is the level supported and can the user actually set the value. In most cases, neither should be an issue
|
||||||
although there are circumstances where this changes. An example of a safe call is:
|
although there are circumstances where this changes. An example of a safe call is:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const isSupported = SettingsStore.isLevelSupported(SettingLevel.ROOM);
|
const isSupported = SettingsStore.isLevelSupported(SettingLevel.ROOM);
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
|
@ -73,19 +74,14 @@ instance, the component which allows changing the setting may be hidden conditio
|
||||||
|
|
||||||
Where possible, the `SettingsFlag` component should be used to set simple "flip-a-bit" (true/false) settings. The
|
Where possible, the `SettingsFlag` component should be used to set simple "flip-a-bit" (true/false) settings. The
|
||||||
`SettingsFlag` also supports simple radio button options, such as the theme the user would like to use.
|
`SettingsFlag` also supports simple radio button options, such as the theme the user would like to use.
|
||||||
```html
|
|
||||||
<SettingsFlag name="theSettingId"
|
|
||||||
level={SettingsLevel.ROOM}
|
|
||||||
roomId="!curbf:matrix.org"
|
|
||||||
label={_td("Your label here")} // optional, if falsey then the `SettingsStore` will be used
|
|
||||||
onChange={function(newValue) { }} // optional, called after saving
|
|
||||||
isExplicit={false} // this is passed along to `SettingsStore.getValueAt`, defaulting to false
|
|
||||||
manualSave={false} // if true, saving is delayed. You will need to call .save() on this component
|
|
||||||
|
|
||||||
// Options for radio buttons
|
```html
|
||||||
group="your-radio-group" // this enables radio button support
|
<SettingsFlag name="theSettingId" level={SettingsLevel.ROOM} roomId="!curbf:matrix.org" label={_td("Your label here")}
|
||||||
value="yourValueHere" // the value for this particular option
|
// optional, if falsey then the `SettingsStore` will be used onChange={function(newValue) { }} // optional, called after
|
||||||
/>
|
saving isExplicit={false} // this is passed along to `SettingsStore.getValueAt`, defaulting to false manualSave={false}
|
||||||
|
// if true, saving is delayed. You will need to call .save() on this component // Options for radio buttons
|
||||||
|
group="your-radio-group" // this enables radio button support value="yourValueHere" // the value for this particular
|
||||||
|
option />
|
||||||
```
|
```
|
||||||
|
|
||||||
### Getting the display name for a setting
|
### Getting the display name for a setting
|
||||||
|
@ -93,16 +89,16 @@ Where possible, the `SettingsFlag` component should be used to set simple "flip-
|
||||||
Simply call `SettingsStore.getDisplayName`. The appropriate display name will be returned and automatically translated
|
Simply call `SettingsStore.getDisplayName`. The appropriate display name will be returned and automatically translated
|
||||||
for you. If a display name cannot be found, it will return `null`.
|
for you. If a display name cannot be found, it will return `null`.
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
Feature flags are just like regular settings with some underlying semantics for how they are meant to be used. Usually
|
Feature flags are just like regular settings with some underlying semantics for how they are meant to be used. Usually
|
||||||
a feature flag is used when a portion of the application is under development or not ready for full release yet, such
|
a feature flag is used when a portion of the application is under development or not ready for full release yet, such
|
||||||
as new functionality or experimental ideas. In these cases, the feature name *should* be named with the `feature_*`
|
as new functionality or experimental ideas. In these cases, the feature name _should_ be named with the `feature_*`
|
||||||
convention and must be tagged with `isFeature: true` in the setting definition. By doing so, the feature will automatically
|
convention and must be tagged with `isFeature: true` in the setting definition. By doing so, the feature will automatically
|
||||||
appear in the "labs" section of the user's settings.
|
appear in the "labs" section of the user's settings.
|
||||||
|
|
||||||
Features can be controlled at the config level using the following structure:
|
Features can be controlled at the config level using the following structure:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"features": {
|
"features": {
|
||||||
"feature_lazyloading": true
|
"feature_lazyloading": true
|
||||||
|
@ -144,7 +140,6 @@ additional steps to actually enable notifications.
|
||||||
|
|
||||||
For more information, see `src/settings/controllers/SettingController.ts`.
|
For more information, see `src/settings/controllers/SettingController.ts`.
|
||||||
|
|
||||||
|
|
||||||
## Local echo
|
## Local echo
|
||||||
|
|
||||||
`SettingsStore` will perform local echo on all settings to ensure that immediately getting values does not cause a
|
`SettingsStore` will perform local echo on all settings to ensure that immediately getting values does not cause a
|
||||||
|
@ -160,7 +155,6 @@ SettingsStore.setValue(...).then(() => {
|
||||||
SettingsStore.getValue(...); // this will return the value set in `setValue` above.
|
SettingsStore.getValue(...); // this will return the value set in `setValue` above.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Watching for changes
|
## Watching for changes
|
||||||
|
|
||||||
Most use cases do not need to set up a watcher because they are able to react to changes as they are made, or the
|
Most use cases do not need to set up a watcher because they are able to react to changes as they are made, or the
|
||||||
|
@ -174,7 +168,6 @@ An example of a watcher in action would be:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class MyComponent extends React.Component {
|
class MyComponent extends React.Component {
|
||||||
|
|
||||||
settingWatcherRef = null;
|
settingWatcherRef = null;
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -190,7 +183,6 @@ class MyComponent extends React.Component {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
# Maintainers Reference
|
# Maintainers Reference
|
||||||
|
|
||||||
The granular settings system has a few complex parts to power it. This section is to document how the `SettingsStore` is
|
The granular settings system has a few complex parts to power it. This section is to document how the `SettingsStore` is
|
||||||
|
|
|
@ -13,12 +13,12 @@ It exposes a function over a postMessage API, when sent an object with the match
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"imgSrc": "", // the src of the image to display in the download link
|
imgSrc: "", // the src of the image to display in the download link
|
||||||
"imgStyle": "", // the style to apply to the image
|
imgStyle: "", // the style to apply to the image
|
||||||
"style": "", // the style to apply to the download link
|
style: "", // the style to apply to the download link
|
||||||
"download": "", // download attribute to pass to the <a/> tag
|
download: "", // download attribute to pass to the <a/> tag
|
||||||
"textContent": "", // the text to put inside the download link
|
textContent: "", // the text to put inside the download link
|
||||||
"blob": "", // the data blob to wrap in an object url and allow the user to download
|
blob: "", // the data blob to wrap in an object url and allow the user to download
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,20 @@ Rooms can have a default widget layout to auto-pin certain widgets, make the con
|
||||||
sizes, etc. These are defined through the `io.element.widgets.layout` state event (empty state key).
|
sizes, etc. These are defined through the `io.element.widgets.layout` state event (empty state key).
|
||||||
|
|
||||||
Full example content:
|
Full example content:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"widgets": {
|
widgets: {
|
||||||
"first-widget-id": {
|
"first-widget-id": {
|
||||||
"container": "top",
|
container: "top",
|
||||||
"index": 0,
|
index: 0,
|
||||||
"width": 60,
|
width: 60,
|
||||||
"height": 40
|
height: 40,
|
||||||
},
|
},
|
||||||
"second-widget-id": {
|
"second-widget-id": {
|
||||||
"container": "right"
|
container: "right",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
subprojects:
|
subprojects:
|
||||||
matrix-js-sdk:
|
matrix-js-sdk:
|
||||||
includeByDefault: false
|
includeByDefault: false
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,15 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes mx--anim-pulse {
|
@keyframes mx--anim-pulse {
|
||||||
0% { opacity: 1; }
|
0% {
|
||||||
50% { opacity: 0.7; }
|
opacity: 1;
|
||||||
100% { opacity: 1; }
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes mx_Dialog_lightbox_background_keyframes {
|
@keyframes mx_Dialog_lightbox_background_keyframes {
|
||||||
|
|
|
@ -23,7 +23,7 @@ limitations under the License.
|
||||||
@import "./_spacing.pcss";
|
@import "./_spacing.pcss";
|
||||||
@import url("maplibre-gl/dist/maplibre-gl.css");
|
@import url("maplibre-gl/dist/maplibre-gl.css");
|
||||||
|
|
||||||
$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); /* quadratic */
|
$hover-transition: 0.08s cubic-bezier(0.46, 0.03, 0.52, 0.96); /* quadratic */
|
||||||
|
|
||||||
$selected-message-border-width: 4px;
|
$selected-message-border-width: 4px;
|
||||||
|
|
||||||
|
@ -40,8 +40,8 @@ $timeline-image-border-radius: 8px;
|
||||||
:root {
|
:root {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
|
||||||
--transition-short: .1s;
|
--transition-short: 0.1s;
|
||||||
--transition-standard: .3s;
|
--transition-standard: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion) {
|
@media (prefers-reduced-motion) {
|
||||||
|
@ -74,13 +74,16 @@ body {
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre, code {
|
pre,
|
||||||
|
code {
|
||||||
font-family: $monospace-font-family;
|
font-family: $monospace-font-family;
|
||||||
font-size: 100% !important;
|
font-size: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error, .warning,
|
.error,
|
||||||
.text-error, .text-warning {
|
.warning,
|
||||||
|
.text-error,
|
||||||
|
.text-warning {
|
||||||
color: $alert;
|
color: $alert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +135,7 @@ input[type="search"].mx_textinput_icon {
|
||||||
/* FIXME THEME - Tint by CSS rather than referencing a duplicate asset */
|
/* FIXME THEME - Tint by CSS rather than referencing a duplicate asset */
|
||||||
input[type="text"].mx_textinput_icon.mx_textinput_search,
|
input[type="text"].mx_textinput_icon.mx_textinput_search,
|
||||||
input[type="search"].mx_textinput_icon.mx_textinput_search {
|
input[type="search"].mx_textinput_icon.mx_textinput_search {
|
||||||
background-image: url('$(res)/img/feather-customised/search-input.svg');
|
background-image: url("$(res)/img/feather-customised/search-input.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* dont search UI as not all browsers support it, */
|
/* dont search UI as not all browsers support it, */
|
||||||
|
@ -150,7 +153,9 @@ textarea::placeholder {
|
||||||
opacity: initial;
|
opacity: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"], input[type="password"], textarea {
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
textarea {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: $primary-content;
|
color: $primary-content;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +165,9 @@ textarea {
|
||||||
color: $primary-content;
|
color: $primary-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
|
input[type="text"]:focus,
|
||||||
|
input[type="password"]:focus,
|
||||||
|
textarea:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +204,8 @@ legend {
|
||||||
/* it has the appearance of a text box so the controls */
|
/* it has the appearance of a text box so the controls */
|
||||||
/* appear to be part of the input */
|
/* appear to be part of the input */
|
||||||
|
|
||||||
.mx_Dialog, .mx_MatrixChat_wrapper {
|
.mx_Dialog,
|
||||||
|
.mx_MatrixChat_wrapper {
|
||||||
.mx_textinput > input[type="text"],
|
.mx_textinput > input[type="text"],
|
||||||
.mx_textinput > input[type="search"] {
|
.mx_textinput > input[type="search"] {
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -213,7 +221,7 @@ legend {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: $input-darker-fg-color;
|
color: $input-darker-fg-color;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid rgba($primary-content, .1);
|
border: 1px solid rgba($primary-content, 0.1);
|
||||||
/* these things should probably not be defined globally */
|
/* these things should probably not be defined globally */
|
||||||
margin: 9px;
|
margin: 9px;
|
||||||
}
|
}
|
||||||
|
@ -226,7 +234,7 @@ legend {
|
||||||
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="text"]::placeholder,
|
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="text"]::placeholder,
|
||||||
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="search"]::placeholder,
|
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="search"]::placeholder,
|
||||||
.mx_textinput input::placeholder {
|
.mx_textinput input::placeholder {
|
||||||
color: rgba($input-darker-fg-color, .75);
|
color: rgba($input-darker-fg-color, 0.75);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -457,7 +465,7 @@ legend {
|
||||||
}
|
}
|
||||||
|
|
||||||
@define-mixin customisedCancelButton {
|
@define-mixin customisedCancelButton {
|
||||||
mask: url('$(res)/img/cancel.svg');
|
mask: url("$(res)/img/cancel.svg");
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: cover;
|
mask-size: cover;
|
||||||
|
@ -768,7 +776,7 @@ legend {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: $button-fg-color;
|
background-color: $button-fg-color;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
|
@ -789,7 +797,7 @@ legend {
|
||||||
@define-mixin ThreadSummaryIcon {
|
@define-mixin ThreadSummaryIcon {
|
||||||
content: "";
|
content: "";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
mask-image: url('$(res)/img/element-icons/thread-summary.svg');
|
mask-image: url("$(res)/img/element-icons/thread-summary.svg");
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
|
@ -817,7 +825,7 @@ legend {
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
left: 3px;
|
left: 3px;
|
||||||
|
@ -830,7 +838,7 @@ legend {
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -24,7 +24,7 @@ $font-6px: 0.6rem;
|
||||||
$font-7px: 0.7rem;
|
$font-7px: 0.7rem;
|
||||||
$font-8px: 0.8rem;
|
$font-8px: 0.8rem;
|
||||||
$font-9px: 0.9rem;
|
$font-9px: 0.9rem;
|
||||||
$font-10px: 1.0rem;
|
$font-10px: 1rem;
|
||||||
$font-10-4px: 1.04rem;
|
$font-10-4px: 1.04rem;
|
||||||
$font-11px: 1.1rem;
|
$font-11px: 1.1rem;
|
||||||
$font-12px: 1.2rem;
|
$font-12px: 1.2rem;
|
||||||
|
@ -35,7 +35,7 @@ $font-16px: 1.6rem;
|
||||||
$font-17px: 1.7rem;
|
$font-17px: 1.7rem;
|
||||||
$font-18px: 1.8rem;
|
$font-18px: 1.8rem;
|
||||||
$font-19px: 1.9rem;
|
$font-19px: 1.9rem;
|
||||||
$font-20px: 2.0rem;
|
$font-20px: 2rem;
|
||||||
$font-21px: 2.1rem;
|
$font-21px: 2.1rem;
|
||||||
$font-22px: 2.2rem;
|
$font-22px: 2.2rem;
|
||||||
$font-23px: 2.3rem;
|
$font-23px: 2.3rem;
|
||||||
|
@ -45,7 +45,7 @@ $font-26px: 2.6rem;
|
||||||
$font-27px: 2.7rem;
|
$font-27px: 2.7rem;
|
||||||
$font-28px: 2.8rem;
|
$font-28px: 2.8rem;
|
||||||
$font-29px: 2.9rem;
|
$font-29px: 2.9rem;
|
||||||
$font-30px: 3.0rem;
|
$font-30px: 3rem;
|
||||||
$font-31px: 3.1rem;
|
$font-31px: 3.1rem;
|
||||||
$font-32px: 3.2rem;
|
$font-32px: 3.2rem;
|
||||||
$font-33px: 3.3rem;
|
$font-33px: 3.3rem;
|
||||||
|
@ -55,7 +55,7 @@ $font-36px: 3.6rem;
|
||||||
$font-37px: 3.7rem;
|
$font-37px: 3.7rem;
|
||||||
$font-38px: 3.8rem;
|
$font-38px: 3.8rem;
|
||||||
$font-39px: 3.9rem;
|
$font-39px: 3.9rem;
|
||||||
$font-40px: 4.0rem;
|
$font-40px: 4rem;
|
||||||
$font-41px: 4.1rem;
|
$font-41px: 4.1rem;
|
||||||
$font-42px: 4.2rem;
|
$font-42px: 4.2rem;
|
||||||
$font-43px: 4.3rem;
|
$font-43px: 4.3rem;
|
||||||
|
@ -65,7 +65,7 @@ $font-46px: 4.6rem;
|
||||||
$font-47px: 4.7rem;
|
$font-47px: 4.7rem;
|
||||||
$font-48px: 4.8rem;
|
$font-48px: 4.8rem;
|
||||||
$font-49px: 4.9rem;
|
$font-49px: 4.9rem;
|
||||||
$font-50px: 5.0rem;
|
$font-50px: 5rem;
|
||||||
$font-51px: 5.1rem;
|
$font-51px: 5.1rem;
|
||||||
$font-52px: 5.2rem;
|
$font-52px: 5.2rem;
|
||||||
$font-78px: 7.8rem;
|
$font-78px: 7.8rem;
|
||||||
|
|
|
@ -31,7 +31,7 @@ limitations under the License.
|
||||||
|
|
||||||
/* caret down */
|
/* caret down */
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
border-top: 5px solid currentColor;
|
border-top: 5px solid currentColor;
|
||||||
|
|
|
@ -33,7 +33,8 @@ limitations under the License.
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $spacing-16;
|
top: $spacing-16;
|
||||||
|
|
||||||
&:hover, &:focus {
|
&:hover,
|
||||||
|
&:focus {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ limitations under the License.
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
opacity: 1.0;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ContextualMenu {
|
.mx_ContextualMenu {
|
||||||
|
|
|
@ -119,5 +119,5 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_FilePanel_empty::before {
|
.mx_FilePanel_empty::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/files.svg');
|
mask-image: url("$(res)/img/element-icons/room/files.svg");
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ limitations under the License.
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #fff; /* on all themes */
|
background-color: #fff; /* on all themes */
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
|
@ -93,15 +93,15 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_HomePage_button_sendDm::before {
|
&.mx_HomePage_button_sendDm::before {
|
||||||
mask-image: url('$(res)/img/element-icons/feedback.svg');
|
mask-image: url("$(res)/img/element-icons/feedback.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_HomePage_button_explore::before {
|
&.mx_HomePage_button_explore::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
mask-image: url("$(res)/img/element-icons/roomlist/explore.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_HomePage_button_createGroup::before {
|
&.mx_HomePage_button_createGroup::before {
|
||||||
mask-image: url('$(res)/img/element-icons/group-members.svg');
|
mask-image: url("$(res)/img/element-icons/group-members.svg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,13 +131,13 @@ $roomListCollapsedWidth: 68px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
mask-image: url('$(res)/img/element-icons/call/dialpad.svg');
|
mask-image: url("$(res)/img/element-icons/call/dialpad.svg");
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
|
@ -155,7 +155,7 @@ $roomListCollapsedWidth: 68px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
|
@ -177,11 +177,11 @@ $roomListCollapsedWidth: 68px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LeftPanel_exploreButton::before {
|
.mx_LeftPanel_exploreButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
mask-image: url("$(res)/img/element-icons/roomlist/explore.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LeftPanel_recentsButton::before {
|
.mx_LeftPanel_recentsButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/clock.svg');
|
mask-image: url("$(res)/img/element-icons/clock.svg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ limitations under the License.
|
||||||
width: 4px;
|
width: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
content: '';
|
content: "";
|
||||||
|
|
||||||
background-color: $primary-content;
|
background-color: $primary-content;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
|
@ -52,7 +52,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_MatrixChat_syncError {
|
.mx_MatrixChat_syncError {
|
||||||
color: $accent-fg-color;
|
color: $accent-fg-color;
|
||||||
background-color: #DF2A8B; /* Only used here */
|
background-color: #df2a8b; /* Only used here */
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
display: table;
|
display: table;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
|
@ -107,7 +107,7 @@ limitations under the License.
|
||||||
width: 4px;
|
width: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
content: ' ';
|
content: " ";
|
||||||
|
|
||||||
background-color: $primary-content;
|
background-color: $primary-content;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
|
@ -80,7 +80,7 @@ limitations under the License.
|
||||||
background-color: $tertiary-content;
|
background-color: $tertiary-content;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
content: '';
|
content: "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,5 +109,5 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel_empty::before {
|
.mx_NotificationPanel_empty::before {
|
||||||
mask-image: url('$(res)/img/element-icons/notifications.svg');
|
mask-image: url("$(res)/img/element-icons/notifications.svg");
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ limitations under the License.
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
left: 0;
|
left: 0;
|
||||||
mask-image: url('$(res)/img/element-icons/settings.svg');
|
mask-image: url("$(res)/img/element-icons/settings.svg");
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 16px;
|
mask-size: 16px;
|
||||||
|
|
|
@ -59,7 +59,7 @@ limitations under the License.
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 4px; /* center with parent of 32px */
|
top: 4px; /* center with parent of 32px */
|
||||||
left: 4px; /* center with parent of 32px */
|
left: 4px; /* center with parent of 32px */
|
||||||
|
@ -80,16 +80,16 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RightPanel_threadsButton::before {
|
.mx_RightPanel_threadsButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/thread.svg');
|
mask-image: url("$(res)/img/element-icons/room/thread.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RightPanel_notifsButton::before {
|
.mx_RightPanel_notifsButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/notifications.svg');
|
mask-image: url("$(res)/img/element-icons/notifications.svg");
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RightPanel_roomSummaryButton::before {
|
.mx_RightPanel_roomSummaryButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
mask-image: url("$(res)/img/element-icons/room/room-summary.svg");
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ $pulse-color: $alert;
|
||||||
|
|
||||||
.mx_RightPanel_pinnedMessagesButton {
|
.mx_RightPanel_pinnedMessagesButton {
|
||||||
&::before {
|
&::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/pin.svg');
|
mask-image: url("$(res)/img/element-icons/room/pin.svg");
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ $pulse-color: $alert;
|
||||||
|
|
||||||
.mx_RightPanel_timelineCardButton {
|
.mx_RightPanel_timelineCardButton {
|
||||||
&::before {
|
&::before {
|
||||||
mask-image: url('$(res)/img/element-icons/feedback.svg');
|
mask-image: url("$(res)/img/element-icons/feedback.svg");
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ $pulse-color: $alert;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
margin: 11px auto 29px auto;
|
margin: 11px auto 29px auto;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue