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:
|
||||||
|
|
6
.github/renovate.json
vendored
6
.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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
52
.github/workflows/backport.yml
vendored
52
.github/workflows/backport.yml
vendored
|
@ -1,30 +1,30 @@
|
||||||
name: Backport
|
name: Backport
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types:
|
types:
|
||||||
- closed
|
- closed
|
||||||
- labeled
|
- labeled
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backport:
|
backport:
|
||||||
name: Backport
|
name: Backport
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Only react to merged PRs for security reasons.
|
# Only react to merged PRs for security reasons.
|
||||||
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
||||||
if: >
|
if: >
|
||||||
github.event.pull_request.merged
|
github.event.pull_request.merged
|
||||||
&& (
|
&& (
|
||||||
github.event.action == 'closed'
|
github.event.action == 'closed'
|
||||||
|| (
|
|| (
|
||||||
github.event.action == 'labeled'
|
github.event.action == 'labeled'
|
||||||
&& contains(github.event.label.name, 'backport')
|
&& contains(github.event.label.name, 'backport')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
steps:
|
steps:
|
||||||
- uses: tibdex/backport@v2
|
- uses: tibdex/backport@v2
|
||||||
with:
|
with:
|
||||||
labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
|
labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
|
||||||
# We can't use GITHUB_TOKEN here or CI won't run on the new PR
|
# We can't use GITHUB_TOKEN here or CI won't run on the new PR
|
||||||
github_token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github_token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
311
.github/workflows/cypress.yaml
vendored
311
.github/workflows/cypress.yaml
vendored
|
@ -1,172 +1,175 @@
|
||||||
# Triggers after the layered build has finished, taking the artifact and running cypress on it
|
# Triggers after the layered build has finished, taking the artifact and running cypress on it
|
||||||
name: Cypress End to End Tests
|
name: Cypress End to End Tests
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: [ "Element Web - Build" ]
|
workflows: ["Element Web - Build"]
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare:
|
||||||
name: Prepare
|
name: Prepare
|
||||||
if: github.event.workflow_run.conclusion == 'success'
|
if: github.event.workflow_run.conclusion == 'success'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
issues: read
|
issues: read
|
||||||
statuses: write
|
statuses: write
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
outputs:
|
outputs:
|
||||||
uuid: ${{ steps.uuid.outputs.value }}
|
uuid: ${{ steps.uuid.outputs.value }}
|
||||||
pr_id: ${{ steps.prdetails.outputs.pr_id }}
|
pr_id: ${{ steps.prdetails.outputs.pr_id }}
|
||||||
commit_message: ${{ steps.commit.outputs.message }}
|
commit_message: ${{ steps.commit.outputs.message }}
|
||||||
commit_author: ${{ steps.commit.outputs.author }}
|
commit_author: ${{ steps.commit.outputs.author }}
|
||||||
commit_email: ${{ steps.commit.outputs.email }}
|
commit_email: ${{ steps.commit.outputs.email }}
|
||||||
percy_enable: ${{ steps.percy.outputs.value || '1' }}
|
percy_enable: ${{ steps.percy.outputs.value || '1' }}
|
||||||
steps:
|
steps:
|
||||||
# We create the status here and then update it to success/failure in the `report` stage
|
# We create the status here and then update it to success/failure in the `report` stage
|
||||||
# This provides an easy link to this workflow_run from the PR before Cypress is done.
|
# This provides an easy link to this workflow_run from the PR before Cypress is done.
|
||||||
- uses: Sibz/github-status-action@v1
|
- uses: Sibz/github-status-action@v1
|
||||||
with:
|
with:
|
||||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
state: pending
|
state: pending
|
||||||
context: ${{ github.workflow }} / cypress (${{ github.event.workflow_run.event }} => ${{ github.event_name }})
|
context: ${{ github.workflow }} / cypress (${{ github.event.workflow_run.event }} => ${{ github.event_name }})
|
||||||
sha: ${{ github.event.workflow_run.head_sha }}
|
sha: ${{ github.event.workflow_run.head_sha }}
|
||||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
||||||
- id: prdetails
|
- id: prdetails
|
||||||
if: github.event.workflow_run.event == 'pull_request'
|
if: github.event.workflow_run.event == 'pull_request'
|
||||||
uses: matrix-org/pr-details-action@v1.2
|
uses: matrix-org/pr-details-action@v1.2
|
||||||
with:
|
with:
|
||||||
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
|
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
|
||||||
branch: ${{ github.event.workflow_run.head_branch }}
|
branch: ${{ github.event.workflow_run.head_branch }}
|
||||||
|
|
||||||
- name: Get commit details
|
- name: Get commit details
|
||||||
id: commit
|
id: commit
|
||||||
if: github.event.workflow_run.event == 'pull_request'
|
if: github.event.workflow_run.event == 'pull_request'
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const response = await github.rest.git.getCommit({
|
const response = await github.rest.git.getCommit({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
commit_sha: "${{ github.event.workflow_run.head_sha }}",
|
commit_sha: "${{ github.event.workflow_run.head_sha }}",
|
||||||
});
|
});
|
||||||
core.setOutput("message", response.data.message);
|
core.setOutput("message", response.data.message);
|
||||||
core.setOutput("author", response.data.author.name);
|
core.setOutput("author", response.data.author.name);
|
||||||
core.setOutput("email", response.data.author.email);
|
core.setOutput("email", response.data.author.email);
|
||||||
|
|
||||||
# Only run Percy when it is demanded or on develop
|
# Only run Percy when it is demanded or on develop
|
||||||
- name: Disable Percy if not needed
|
- name: Disable Percy if not needed
|
||||||
id: percy
|
id: percy
|
||||||
if: |
|
if: |
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
github.event.workflow_run.event == 'pull_request' &&
|
||||||
!contains(fromJSON(steps.prdetails.outputs.data).labels.*.name, 'X-Needs-Percy')
|
!contains(fromJSON(steps.prdetails.outputs.data).labels.*.name, 'X-Needs-Percy')
|
||||||
run: echo "::set-output name=value::0"
|
run: echo "::set-output name=value::0"
|
||||||
|
|
||||||
- name: Generate unique ID 💎
|
- name: Generate unique ID 💎
|
||||||
id: uuid
|
id: uuid
|
||||||
run: echo "::set-output name=value::sha-$GITHUB_SHA-time-$(date +"%s")"
|
run: echo "::set-output name=value::sha-$GITHUB_SHA-time-$(date +"%s")"
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: "Run Tests"
|
name: "Run Tests"
|
||||||
needs: prepare
|
needs: prepare
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
issues: read
|
issues: read
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
environment: Cypress
|
environment:
|
||||||
#strategy:
|
Cypress
|
||||||
# fail-fast: false
|
#strategy:
|
||||||
# matrix:
|
# fail-fast: false
|
||||||
# # Run 4 instances in Parallel
|
# matrix:
|
||||||
# runner: [1, 2, 3, 4]
|
# # Run 4 instances in Parallel
|
||||||
steps:
|
# runner: [1, 2, 3, 4]
|
||||||
- uses: actions/checkout@v3
|
steps:
|
||||||
with:
|
- uses: actions/checkout@v3
|
||||||
# XXX: We're checking out untrusted code in a secure context
|
with:
|
||||||
# We need to be careful to not trust anything this code outputs/may do
|
# XXX: We're checking out untrusted code in a secure context
|
||||||
# We need to check this out to access the cypress tests which are on the head branch
|
# We need to be careful to not trust anything this code outputs/may do
|
||||||
repository: ${{ github.event.workflow_run.head_repository.full_name }}
|
# We need to check this out to access the cypress tests which are on the head branch
|
||||||
ref: ${{ github.event.workflow_run.head_sha }}
|
repository: ${{ github.event.workflow_run.head_repository.full_name }}
|
||||||
persist-credentials: false
|
ref: ${{ github.event.workflow_run.head_sha }}
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
|
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
|
||||||
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
|
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
|
||||||
- name: 📥 Download artifact
|
- name: 📥 Download artifact
|
||||||
uses: dawidd6/action-download-artifact@v2
|
uses: dawidd6/action-download-artifact@v2
|
||||||
with:
|
with:
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
name: previewbuild
|
name: previewbuild
|
||||||
path: webapp
|
path: webapp
|
||||||
|
|
||||||
- name: Run Cypress tests
|
- name: Run Cypress tests
|
||||||
uses: cypress-io/github-action@v4.2.2
|
uses: cypress-io/github-action@v4.2.2
|
||||||
with:
|
with:
|
||||||
# The built-in Electron runner seems to grind to a halt trying
|
# The built-in Electron runner seems to grind to a halt trying
|
||||||
# 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:
|
||||||
#parallel: true
|
true
|
||||||
#command-prefix: 'yarn percy exec --parallel --'
|
#parallel: true
|
||||||
command-prefix: 'yarn percy exec --'
|
#command-prefix: 'yarn percy exec --parallel --'
|
||||||
ci-build-id: ${{ needs.prepare.outputs.uuid }}
|
command-prefix: "yarn percy exec --"
|
||||||
env:
|
ci-build-id: ${{ needs.prepare.outputs.uuid }}
|
||||||
# pass the Dashboard record key as an environment variable
|
env:
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
# pass the Dashboard record key as an environment variable
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
|
||||||
# Use existing chromium rather than downloading another
|
# Use existing chromium rather than downloading another
|
||||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
||||||
|
|
||||||
# pass GitHub token to allow accurately detecting a build vs a re-run build
|
# pass GitHub token to allow accurately detecting a build vs a re-run build
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# make Node's os.tmpdir() return something where we actually have permissions
|
# make Node's os.tmpdir() return something where we actually have permissions
|
||||||
TMPDIR: ${{ runner.temp }}
|
TMPDIR: ${{ runner.temp }}
|
||||||
|
|
||||||
# tell Cypress more details about the context of this run
|
# tell Cypress more details about the context of this run
|
||||||
COMMIT_INFO_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
COMMIT_INFO_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||||
COMMIT_INFO_SHA: ${{ github.event.workflow_run.head_sha }}
|
COMMIT_INFO_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
COMMIT_INFO_REMOTE: ${{ github.repositoryUrl }}
|
COMMIT_INFO_REMOTE: ${{ github.repositoryUrl }}
|
||||||
COMMIT_INFO_MESSAGE: ${{ needs.prepare.outputs.commit_message }}
|
COMMIT_INFO_MESSAGE: ${{ needs.prepare.outputs.commit_message }}
|
||||||
COMMIT_INFO_AUTHOR: ${{ needs.prepare.outputs.commit_author }}
|
COMMIT_INFO_AUTHOR: ${{ needs.prepare.outputs.commit_author }}
|
||||||
COMMIT_INFO_EMAIL: ${{ needs.prepare.outputs.commit_email }}
|
COMMIT_INFO_EMAIL: ${{ needs.prepare.outputs.commit_email }}
|
||||||
|
|
||||||
# pass the Percy token as an environment variable
|
# pass the Percy token as an environment variable
|
||||||
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
||||||
PERCY_ENABLE: ${{ needs.prepare.outputs.percy_enable }}
|
PERCY_ENABLE: ${{ needs.prepare.outputs.percy_enable }}
|
||||||
PERCY_BROWSER_EXECUTABLE: /usr/bin/chromium-browser
|
PERCY_BROWSER_EXECUTABLE: /usr/bin/chromium-browser
|
||||||
# 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:
|
||||||
#PERCY_PARALLEL_TOTAL: ${{ strategy.job-total }}
|
${{ needs.prepare.outputs.pr_id }}
|
||||||
PERCY_PARALLEL_NONCE: ${{ needs.prepare.outputs.uuid }}
|
#PERCY_PARALLEL_TOTAL: ${{ strategy.job-total }}
|
||||||
|
PERCY_PARALLEL_NONCE: ${{ needs.prepare.outputs.uuid }}
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: cypress-results
|
name: cypress-results
|
||||||
path: |
|
path: |
|
||||||
cypress/screenshots
|
cypress/screenshots
|
||||||
cypress/videos
|
cypress/videos
|
||||||
cypress/synapselogs
|
cypress/synapselogs
|
||||||
|
|
||||||
report:
|
report:
|
||||||
name: Report results
|
name: Report results
|
||||||
needs: tests
|
needs: tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: always()
|
if: always()
|
||||||
permissions:
|
permissions:
|
||||||
statuses: write
|
statuses: write
|
||||||
steps:
|
steps:
|
||||||
- uses: Sibz/github-status-action@v1
|
- uses: Sibz/github-status-action@v1
|
||||||
with:
|
with:
|
||||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
state: ${{ needs.tests.result == 'success' && 'success' || 'failure' }}
|
state: ${{ needs.tests.result == 'success' && 'success' || 'failure' }}
|
||||||
context: ${{ github.workflow }} / cypress (${{ github.event.workflow_run.event }} => ${{ github.event_name }})
|
context: ${{ github.workflow }} / cypress (${{ github.event.workflow_run.event }} => ${{ github.event_name }})
|
||||||
sha: ${{ github.event.workflow_run.head_sha }}
|
sha: ${{ github.event.workflow_run.head_sha }}
|
||||||
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
84
.github/workflows/element-web.yaml
vendored
84
.github/workflows/element-web.yaml
vendored
|
@ -3,52 +3,52 @@
|
||||||
# as an artifact and run integration tests.
|
# as an artifact and run integration tests.
|
||||||
name: Element Web - Build
|
name: Element Web - Build
|
||||||
on:
|
on:
|
||||||
pull_request: { }
|
pull_request: {}
|
||||||
push:
|
push:
|
||||||
branches: [ develop, master ]
|
branches: [develop, master]
|
||||||
repository_dispatch:
|
repository_dispatch:
|
||||||
types: [ upstream-sdk-notify ]
|
types: [upstream-sdk-notify]
|
||||||
env:
|
env:
|
||||||
# These must be set for fetchdep.sh to get the right branch
|
# These must be set for fetchdep.sh to get the right branch
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: "Build Element-Web"
|
name: "Build Element-Web"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- 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
|
||||||
run: |
|
run: |
|
||||||
scripts/ci/layered.sh
|
scripts/ci/layered.sh
|
||||||
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
||||||
REACT_SHA=$(git rev-parse --short=12 HEAD)
|
REACT_SHA=$(git rev-parse --short=12 HEAD)
|
||||||
VECTOR_SHA=$(git -C element-web rev-parse --short=12 HEAD)
|
VECTOR_SHA=$(git -C element-web rev-parse --short=12 HEAD)
|
||||||
echo "::set-output name=VERSION::$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA"
|
echo "::set-output name=VERSION::$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA"
|
||||||
|
|
||||||
- name: Copy config
|
- name: Copy config
|
||||||
run: cp element.io/develop/config.json config.json
|
run: cp element.io/develop/config.json config.json
|
||||||
working-directory: ./element-web
|
working-directory: ./element-web
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
CI_PACKAGE: true
|
CI_PACKAGE: true
|
||||||
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||||
run: |
|
run: |
|
||||||
yarn build
|
yarn build
|
||||||
echo $VERSION > webapp/version
|
echo $VERSION > webapp/version
|
||||||
working-directory: ./element-web
|
working-directory: ./element-web
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: previewbuild
|
name: previewbuild
|
||||||
path: element-web/webapp
|
path: element-web/webapp
|
||||||
# We'll only use this in a triggered job, then we're done with it
|
# We'll only use this in a triggered job, then we're done with it
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
64
.github/workflows/i18n_check.yml
vendored
64
.github/workflows/i18n_check.yml
vendored
|
@ -1,40 +1,40 @@
|
||||||
name: i18n Check
|
name: i18n Check
|
||||||
on:
|
on:
|
||||||
workflow_call: { }
|
workflow_call: {}
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: "Get modified files"
|
- name: "Get modified files"
|
||||||
id: changed_files
|
id: changed_files
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.user.login != 'RiotTranslateBot'
|
if: github.event_name == 'pull_request' && github.event.pull_request.user.login != 'RiotTranslateBot'
|
||||||
uses: tj-actions/changed-files@v34
|
uses: tj-actions/changed-files@v34
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
src/i18n/strings/*
|
src/i18n/strings/*
|
||||||
files_ignore: |
|
files_ignore: |
|
||||||
src/i18n/strings/en_EN.json
|
src/i18n/strings/en_EN.json
|
||||||
|
|
||||||
- name: "Assert only en_EN was modified"
|
- name: "Assert only en_EN was modified"
|
||||||
if: |
|
if: |
|
||||||
github.event_name == 'pull_request' &&
|
github.event_name == 'pull_request' &&
|
||||||
github.event.pull_request.user.login != 'RiotTranslateBot' &&
|
github.event.pull_request.user.login != 'RiotTranslateBot' &&
|
||||||
steps.changed_files.outputs.any_modified == 'true'
|
steps.changed_files.outputs.any_modified == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "Only translation files modified by `yarn i18n` can be committed - other translation files will confuse weblate in unrecoverable ways."
|
echo "Only translation files modified by `yarn i18n` can be committed - other translation files will confuse weblate in unrecoverable ways."
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- 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
|
||||||
run: "yarn install --pure-lockfile"
|
run: "yarn install --pure-lockfile"
|
||||||
|
|
||||||
- name: i18n Check
|
- name: i18n Check
|
||||||
run: "yarn run diff-i18n"
|
run: "yarn run diff-i18n"
|
||||||
|
|
124
.github/workflows/netlify.yaml
vendored
124
.github/workflows/netlify.yaml
vendored
|
@ -2,70 +2,70 @@
|
||||||
# and uploading it to netlify
|
# and uploading it to netlify
|
||||||
name: Upload Preview Build to Netlify
|
name: Upload Preview Build to Netlify
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: [ "Element Web - Build" ]
|
workflows: ["Element Web - Build"]
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
|
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: Netlify
|
environment: Netlify
|
||||||
steps:
|
steps:
|
||||||
- name: 📝 Create Deployment
|
- name: 📝 Create Deployment
|
||||||
uses: bobheadxi/deployments@v1
|
uses: bobheadxi/deployments@v1
|
||||||
id: deployment
|
id: deployment
|
||||||
with:
|
with:
|
||||||
step: start
|
step: start
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
env: Netlify
|
env: Netlify
|
||||||
ref: ${{ github.event.workflow_run.head_sha }}
|
ref: ${{ github.event.workflow_run.head_sha }}
|
||||||
desc: |
|
desc: |
|
||||||
Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
|
Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
|
||||||
Exercise caution. Use test accounts.
|
Exercise caution. Use test accounts.
|
||||||
|
|
||||||
- id: prdetails
|
- id: prdetails
|
||||||
uses: matrix-org/pr-details-action@v1.2
|
uses: matrix-org/pr-details-action@v1.2
|
||||||
with:
|
with:
|
||||||
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
|
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
|
||||||
branch: ${{ github.event.workflow_run.head_branch }}
|
branch: ${{ github.event.workflow_run.head_branch }}
|
||||||
|
|
||||||
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
|
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
|
||||||
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
|
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
|
||||||
- name: 📥 Download artifact
|
- name: 📥 Download artifact
|
||||||
uses: dawidd6/action-download-artifact@v2
|
uses: dawidd6/action-download-artifact@v2
|
||||||
with:
|
with:
|
||||||
workflow: element-build-and-test.yaml
|
workflow: element-build-and-test.yaml
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
name: previewbuild
|
name: previewbuild
|
||||||
path: webapp
|
path: webapp
|
||||||
|
|
||||||
- name: ☁️ Deploy to Netlify
|
- name: ☁️ Deploy to Netlify
|
||||||
id: netlify
|
id: netlify
|
||||||
uses: nwtgck/actions-netlify@v1.2
|
uses: nwtgck/actions-netlify@v1.2
|
||||||
with:
|
with:
|
||||||
publish-dir: webapp
|
publish-dir: webapp
|
||||||
deploy-message: "Deploy from GitHub Actions"
|
deploy-message: "Deploy from GitHub Actions"
|
||||||
# These don't work because we're in workflow_run
|
# These don't work because we're in workflow_run
|
||||||
enable-pull-request-comment: false
|
enable-pull-request-comment: false
|
||||||
enable-commit-comment: false
|
enable-commit-comment: false
|
||||||
alias: pr${{ steps.prdetails.outputs.pr_id }}
|
alias: pr${{ steps.prdetails.outputs.pr_id }}
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
|
|
||||||
- name: 🚦 Update deployment status
|
- name: 🚦 Update deployment status
|
||||||
uses: bobheadxi/deployments@v1
|
uses: bobheadxi/deployments@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
step: finish
|
step: finish
|
||||||
override: false
|
override: false
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
status: ${{ job.status }}
|
status: ${{ job.status }}
|
||||||
env: ${{ steps.deployment.outputs.env }}
|
env: ${{ steps.deployment.outputs.env }}
|
||||||
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
|
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
|
||||||
env_url: ${{ steps.netlify.outputs.deploy-url }}
|
env_url: ${{ steps.netlify.outputs.deploy-url }}
|
||||||
desc: |
|
desc: |
|
||||||
Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
|
Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
|
||||||
Exercise caution. Use test accounts.
|
Exercise caution. Use test accounts.
|
||||||
|
|
32
.github/workflows/notify-element-web.yml
vendored
32
.github/workflows/notify-element-web.yml
vendored
|
@ -1,19 +1,19 @@
|
||||||
name: Notify element-web
|
name: Notify element-web
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ develop ]
|
branches: [develop]
|
||||||
repository_dispatch:
|
repository_dispatch:
|
||||||
types: [ upstream-sdk-notify ]
|
types: [upstream-sdk-notify]
|
||||||
jobs:
|
jobs:
|
||||||
notify-element-web:
|
notify-element-web:
|
||||||
name: "Notify Element Web"
|
name: "Notify Element Web"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Only respect triggers from our develop branch, ignore that of forks
|
# Only respect triggers from our develop branch, ignore that of forks
|
||||||
if: github.repository == 'matrix-org/matrix-react-sdk'
|
if: github.repository == 'matrix-org/matrix-react-sdk'
|
||||||
steps:
|
steps:
|
||||||
- name: Notify element-web repo that a new SDK build is on develop
|
- name: Notify element-web repo that a new SDK build is on develop
|
||||||
uses: peter-evans/repository-dispatch@v2
|
uses: peter-evans/repository-dispatch@v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
repository: vector-im/element-web
|
repository: vector-im/element-web
|
||||||
event-type: element-web-notify
|
event-type: element-web-notify
|
||||||
|
|
16
.github/workflows/pull_request.yaml
vendored
16
.github/workflows/pull_request.yaml
vendored
|
@ -1,12 +1,12 @@
|
||||||
name: Pull Request
|
name: Pull Request
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [ opened, edited, labeled, unlabeled, synchronize ]
|
types: [opened, edited, labeled, unlabeled, synchronize]
|
||||||
concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }}
|
||||||
jobs:
|
jobs:
|
||||||
action:
|
action:
|
||||||
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
|
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
|
||||||
with:
|
with:
|
||||||
labels: "T-Defect,T-Enhancement,T-Task"
|
labels: "T-Defect,T-Enhancement,T-Task"
|
||||||
secrets:
|
secrets:
|
||||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
@ -1,11 +1,11 @@
|
||||||
name: Release Process
|
name: Release Process
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ published ]
|
types: [published]
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
jobs:
|
jobs:
|
||||||
npm:
|
npm:
|
||||||
name: Publish
|
name: Publish
|
||||||
uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop
|
uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop
|
||||||
secrets:
|
secrets:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
22
.github/workflows/sonarqube.yml
vendored
22
.github/workflows/sonarqube.yml
vendored
|
@ -1,15 +1,15 @@
|
||||||
name: SonarQube
|
name: SonarQube
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: [ "Tests" ]
|
workflows: ["Tests"]
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }}
|
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
jobs:
|
jobs:
|
||||||
sonarqube:
|
sonarqube:
|
||||||
name: 🩻 SonarQube
|
name: 🩻 SonarQube
|
||||||
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop
|
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop
|
||||||
secrets:
|
secrets:
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|
242
.github/workflows/static_analysis.yaml
vendored
242
.github/workflows/static_analysis.yaml
vendored
|
@ -1,148 +1,148 @@
|
||||||
name: Static Analysis
|
name: Static Analysis
|
||||||
on:
|
on:
|
||||||
pull_request: { }
|
pull_request: {}
|
||||||
push:
|
push:
|
||||||
branches: [ develop, master ]
|
branches: [develop, master]
|
||||||
repository_dispatch:
|
repository_dispatch:
|
||||||
types: [ upstream-sdk-notify ]
|
types: [upstream-sdk-notify]
|
||||||
env:
|
env:
|
||||||
# These must be set for fetchdep.sh to get the right branch
|
# These must be set for fetchdep.sh to get the right branch
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
jobs:
|
jobs:
|
||||||
ts_lint:
|
ts_lint:
|
||||||
name: "Typescript Syntax Check"
|
name: "Typescript Syntax Check"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- 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"
|
||||||
|
|
||||||
- name: Typecheck
|
- name: Typecheck
|
||||||
run: "yarn run lint:types"
|
run: "yarn run lint:types"
|
||||||
|
|
||||||
- name: Switch js-sdk to release mode
|
- name: Switch js-sdk to release mode
|
||||||
working-directory: node_modules/matrix-js-sdk
|
working-directory: node_modules/matrix-js-sdk
|
||||||
run: |
|
run: |
|
||||||
scripts/switch_package_to_release.js
|
scripts/switch_package_to_release.js
|
||||||
yarn install
|
yarn install
|
||||||
yarn run build:compile
|
yarn run build:compile
|
||||||
yarn run build:types
|
yarn run build:types
|
||||||
|
|
||||||
- name: Typecheck (release mode)
|
- name: Typecheck (release mode)
|
||||||
run: "yarn run lint:types"
|
run: "yarn run lint:types"
|
||||||
|
|
||||||
tsc-strict:
|
tsc-strict:
|
||||||
name: Typescript Strict Error Checker
|
name: Typescript Strict Error Checker
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
checks: write
|
checks: write
|
||||||
strategy:
|
strategy:
|
||||||
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
|
||||||
|
|
||||||
- name: Install Deps
|
|
||||||
run: "scripts/ci/layered.sh"
|
|
||||||
|
|
||||||
- name: Get diff lines
|
- name: Install Deps
|
||||||
id: diff
|
run: "scripts/ci/layered.sh"
|
||||||
uses: Equip-Collaboration/diff-line-numbers@v1.0.0
|
|
||||||
with:
|
|
||||||
include: '["\\.tsx?$"]'
|
|
||||||
|
|
||||||
- name: Detecting files changed
|
- name: Get diff lines
|
||||||
id: files
|
id: diff
|
||||||
uses: futuratrepadeira/changed-files@v4.0.0
|
uses: Equip-Collaboration/diff-line-numbers@v1.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
include: '["\\.tsx?$"]'
|
||||||
pattern: '^.*\.tsx?$'
|
|
||||||
|
|
||||||
- uses: t3chguy/typescript-check-action@main
|
- name: Detecting files changed
|
||||||
with:
|
id: files
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
uses: futuratrepadeira/changed-files@v4.0.0
|
||||||
use-check: false
|
with:
|
||||||
check-fail-mode: added
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
output-behaviour: annotate
|
pattern: '^.*\.tsx?$'
|
||||||
ts-extra-args: ${{ matrix.args }}
|
|
||||||
files-changed: ${{ steps.files.outputs.files_updated }}
|
|
||||||
files-added: ${{ steps.files.outputs.files_created }}
|
|
||||||
files-deleted: ${{ steps.files.outputs.files_deleted }}
|
|
||||||
line-numbers: ${{ steps.diff.outputs.lineNumbers }}
|
|
||||||
|
|
||||||
i18n_lint:
|
- uses: t3chguy/typescript-check-action@main
|
||||||
name: "i18n Check"
|
with:
|
||||||
uses: matrix-org/matrix-react-sdk/.github/workflows/i18n_check.yml@develop
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
use-check: false
|
||||||
|
check-fail-mode: added
|
||||||
|
output-behaviour: annotate
|
||||||
|
ts-extra-args: ${{ matrix.args }}
|
||||||
|
files-changed: ${{ steps.files.outputs.files_updated }}
|
||||||
|
files-added: ${{ steps.files.outputs.files_created }}
|
||||||
|
files-deleted: ${{ steps.files.outputs.files_deleted }}
|
||||||
|
line-numbers: ${{ steps.diff.outputs.lineNumbers }}
|
||||||
|
|
||||||
rethemendex_lint:
|
i18n_lint:
|
||||||
name: "Rethemendex Check"
|
name: "i18n Check"
|
||||||
runs-on: ubuntu-latest
|
uses: matrix-org/matrix-react-sdk/.github/workflows/i18n_check.yml@develop
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- run: ./res/css/rethemendex.sh
|
|
||||||
|
|
||||||
- run: git diff --exit-code
|
|
||||||
|
|
||||||
js_lint:
|
rethemendex_lint:
|
||||||
name: "ESLint"
|
name: "Rethemendex Check"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- run: ./res/css/rethemendex.sh
|
||||||
with:
|
|
||||||
cache: 'yarn'
|
|
||||||
|
|
||||||
# Does not need branch matching as only analyses this layer
|
- run: git diff --exit-code
|
||||||
- name: Install Deps
|
|
||||||
run: "yarn install"
|
|
||||||
|
|
||||||
- name: Run Linter
|
js_lint:
|
||||||
run: "yarn run lint:js"
|
name: "ESLint"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
style_lint:
|
- uses: actions/setup-node@v3
|
||||||
name: "Style Lint"
|
with:
|
||||||
runs-on: ubuntu-latest
|
cache: "yarn"
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
# Does not need branch matching as only analyses this layer
|
||||||
with:
|
- name: Install Deps
|
||||||
cache: 'yarn'
|
run: "yarn install"
|
||||||
|
|
||||||
# Does not need branch matching as only analyses this layer
|
- name: Run Linter
|
||||||
- name: Install Deps
|
run: "yarn run lint:js"
|
||||||
run: "yarn install"
|
|
||||||
|
|
||||||
- name: Run Linter
|
style_lint:
|
||||||
run: "yarn run lint:style"
|
name: "Style Lint"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
analyse_dead_code:
|
- uses: actions/setup-node@v3
|
||||||
name: "Analyse Dead Code"
|
with:
|
||||||
runs-on: ubuntu-latest
|
cache: "yarn"
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
# Does not need branch matching as only analyses this layer
|
||||||
with:
|
- name: Install Deps
|
||||||
cache: 'yarn'
|
run: "yarn install"
|
||||||
|
|
||||||
- name: Install Deps
|
- name: Run Linter
|
||||||
run: "scripts/ci/layered.sh"
|
run: "yarn run lint:style"
|
||||||
|
|
||||||
- name: Dead Code Analysis
|
analyse_dead_code:
|
||||||
run: |
|
name: "Analyse Dead Code"
|
||||||
cd element-web
|
runs-on: ubuntu-latest
|
||||||
yarn run analyse:unused-exports
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
cache: "yarn"
|
||||||
|
|
||||||
|
- name: Install Deps
|
||||||
|
run: "scripts/ci/layered.sh"
|
||||||
|
|
||||||
|
- name: Dead Code Analysis
|
||||||
|
run: |
|
||||||
|
cd element-web
|
||||||
|
yarn run analyse:unused-exports
|
||||||
|
|
92
.github/workflows/tests.yml
vendored
92
.github/workflows/tests.yml
vendored
|
@ -1,59 +1,59 @@
|
||||||
name: Tests
|
name: Tests
|
||||||
on:
|
on:
|
||||||
pull_request: { }
|
pull_request: {}
|
||||||
push:
|
push:
|
||||||
branches: [ develop, master ]
|
branches: [develop, master]
|
||||||
repository_dispatch:
|
repository_dispatch:
|
||||||
types: [ upstream-sdk-notify ]
|
types: [upstream-sdk-notify]
|
||||||
env:
|
env:
|
||||||
# These must be set for fetchdep.sh to get the right branch
|
# These must be set for fetchdep.sh to get the right branch
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
jobs:
|
jobs:
|
||||||
jest:
|
jest:
|
||||||
name: Jest
|
name: Jest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- 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"
|
||||||
|
|
||||||
- name: Get number of CPU cores
|
- name: Get number of CPU cores
|
||||||
id: cpu-cores
|
id: cpu-cores
|
||||||
uses: SimenB/github-actions-cpu-cores@v1
|
uses: SimenB/github-actions-cpu-cores@v1
|
||||||
|
|
||||||
- name: Run tests with coverage and metrics
|
- name: Run tests with coverage and metrics
|
||||||
if: github.ref == 'refs/heads/develop'
|
if: github.ref == 'refs/heads/develop'
|
||||||
run: "yarn coverage --ci --reporters github-actions '--reporters=<rootDir>/test/slowReporter.js' --max-workers ${{ steps.cpu-cores.outputs.count }}"
|
run: "yarn coverage --ci --reporters github-actions '--reporters=<rootDir>/test/slowReporter.js' --max-workers ${{ steps.cpu-cores.outputs.count }}"
|
||||||
|
|
||||||
- name: Run tests with coverage
|
- name: Run tests with coverage
|
||||||
if: github.ref != 'refs/heads/develop'
|
if: github.ref != 'refs/heads/develop'
|
||||||
run: "yarn coverage --ci --reporters github-actions --max-workers ${{ steps.cpu-cores.outputs.count }}"
|
run: "yarn coverage --ci --reporters github-actions --max-workers ${{ steps.cpu-cores.outputs.count }}"
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: coverage
|
name: coverage
|
||||||
path: |
|
path: |
|
||||||
coverage
|
coverage
|
||||||
!coverage/lcov-report
|
!coverage/lcov-report
|
||||||
|
|
||||||
app-tests:
|
app-tests:
|
||||||
name: Element Web Integration Tests
|
name: Element Web Integration Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- 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"
|
||||||
|
|
10
.github/workflows/upgrade_dependencies.yml
vendored
10
.github/workflows/upgrade_dependencies.yml
vendored
|
@ -1,8 +1,8 @@
|
||||||
name: Upgrade Dependencies
|
name: Upgrade Dependencies
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: { }
|
workflow_dispatch: {}
|
||||||
jobs:
|
jobs:
|
||||||
upgrade:
|
upgrade:
|
||||||
uses: matrix-org/matrix-js-sdk/.github/workflows/upgrade_dependencies.yml@develop
|
uses: matrix-org/matrix-js-sdk/.github/workflows/upgrade_dependencies.yml@develop
|
||||||
secrets:
|
secrets:
|
||||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
version: 2
|
version: 2
|
||||||
snapshot:
|
snapshot:
|
||||||
widths:
|
widths:
|
||||||
- 1024
|
- 1024
|
||||||
- 1920
|
- 1920
|
||||||
percy:
|
percy:
|
||||||
defer-uploads: true
|
defer-uploads: true
|
||||||
|
|
|
@ -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": [
|
||||||
// https://github.com/vector-im/element-web/issues/10544
|
true,
|
||||||
"ignoreAtRules": ["define-mixin"],
|
{
|
||||||
}],
|
// https://github.com/vector-im/element-web/issues/10544
|
||||||
|
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,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
27848
CHANGELOG.md
27848
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
|
||||||
|
|
115
README.md
115
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,19 +28,19 @@ 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.
|
||||||
iOS (https://github.com/matrix-org/matrix-ios-kit) and Android
|
- Mobile Web is not currently a target platform - instead please use the native
|
||||||
(https://github.com/matrix-org/matrix-android-sdk2) SDKs.
|
iOS (https://github.com/matrix-org/matrix-ios-kit) and Android
|
||||||
|
(https://github.com/matrix-org/matrix-android-sdk2) SDKs.
|
||||||
|
|
||||||
All code lands on the `develop` branch - `master` is only used for stable releases.
|
All code lands on the `develop` branch - `master` is only used for stable releases.
|
||||||
**Please file PRs against `develop`!!**
|
**Please file PRs against `develop`!!**
|
||||||
|
@ -52,22 +52,23 @@ 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:
|
|
||||||
https://github.com/matrix-org/matrix-react-sdk/tree/master/src/components
|
- All new components:
|
||||||
* Element-specific components:
|
https://github.com/matrix-org/matrix-react-sdk/tree/master/src/components
|
||||||
https://github.com/vector-im/element-web/tree/master/src/components
|
- Element-specific components:
|
||||||
* In practice, `matrix-react-sdk` is still evolving so fast that the
|
https://github.com/vector-im/element-web/tree/master/src/components
|
||||||
maintenance burden of customising and overriding these components for
|
- In practice, `matrix-react-sdk` is still evolving so fast that the
|
||||||
Element can seriously impede development. So right now, there should be
|
maintenance burden of customising and overriding these components for
|
||||||
very few (if any) customisations for Element.
|
Element can seriously impede development. So right now, there should be
|
||||||
* CSS: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css
|
very few (if any) customisations for Element.
|
||||||
* Theme specific CSS & resources:
|
- CSS: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css
|
||||||
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes
|
- Theme specific CSS & resources:
|
||||||
|
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:
|
||||||
'structures' and 'views'. Structures are stateful components which handle the
|
'structures' and 'views'. Structures are stateful components which handle the
|
||||||
more complicated business logic of the app, delegating their actual presentation
|
more complicated business logic of the app, delegating their actual presentation
|
||||||
rendering to stateless 'view' components. For instance, the RoomView component
|
rendering to stateless 'view' components. For instance, the RoomView component
|
||||||
that orchestrates the act of visualising the contents of a given Matrix chat
|
that orchestrates the act of visualising the contents of a given Matrix chat
|
||||||
room tracks lots of state for its child components which it passes into them for
|
room tracks lots of state for its child components which it passes into them for
|
||||||
visual rendering via props.
|
visual rendering via props.
|
||||||
|
@ -75,74 +76,72 @@ 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.
|
||||||
|
|
||||||
Originally `matrix-react-sdk` followed the Atomic design pattern as per
|
Originally `matrix-react-sdk` followed the Atomic design pattern as per
|
||||||
http://patternlab.io to try to encourage a modular architecture. However, we
|
http://patternlab.io to try to encourage a modular architecture. However, we
|
||||||
found that the grouping of components into atoms/molecules/organisms
|
found that the grouping of components into atoms/molecules/organisms
|
||||||
made them harder to find relative to a functional split, and didn't emphasise
|
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.
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"en": {
|
"en": {
|
||||||
"fileName": "en_EN.json",
|
"fileName": "en_EN.json",
|
||||||
"label": "English"
|
"label": "English"
|
||||||
},
|
},
|
||||||
"en-us": {
|
"en-us": {
|
||||||
"fileName": "en_US.json",
|
"fileName": "en_US.json",
|
||||||
"label": "English (US)"
|
"label": "English (US)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
"last 2 Chrome versions",
|
{
|
||||||
"last 2 Firefox versions",
|
targets: [
|
||||||
"last 2 Safari versions",
|
"last 2 Chrome versions",
|
||||||
"last 2 Edge versions",
|
"last 2 Firefox versions",
|
||||||
],
|
"last 2 Safari 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);
|
||||||
|
@ -49,7 +49,7 @@ const checkDMRoom = () => {
|
||||||
cy.contains(".mx_RoomView_body .mx_cryptoEvent", "Encryption enabled").should("exist");
|
cy.contains(".mx_RoomView_body .mx_cryptoEvent", "Encryption enabled").should("exist");
|
||||||
};
|
};
|
||||||
|
|
||||||
const startDMWithBob = function(this: CryptoTestContext) {
|
const startDMWithBob = function (this: CryptoTestContext) {
|
||||||
cy.get('.mx_RoomList [aria-label="Start chat"]').click();
|
cy.get('.mx_RoomList [aria-label="Start chat"]').click();
|
||||||
cy.get('[data-testid="invite-dialog-input"]').type(this.bob.getUserId());
|
cy.get('[data-testid="invite-dialog-input"]').type(this.bob.getUserId());
|
||||||
cy.contains(".mx_InviteDialog_tile_nameStack_name", "Bob").click();
|
cy.contains(".mx_InviteDialog_tile_nameStack_name", "Bob").click();
|
||||||
|
@ -57,11 +57,13 @@ const startDMWithBob = function(this: CryptoTestContext) {
|
||||||
cy.get(".mx_InviteDialog_goButton").click();
|
cy.get(".mx_InviteDialog_goButton").click();
|
||||||
};
|
};
|
||||||
|
|
||||||
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!")
|
||||||
cy.get(".mx_EventTile_e2eIcon_warning").should("not.exist");
|
.closest(".mx_EventTile")
|
||||||
});
|
.within(() => {
|
||||||
|
cy.get(".mx_EventTile_e2eIcon_warning").should("not.exist");
|
||||||
|
});
|
||||||
|
|
||||||
// Bob sends a response
|
// Bob sends a response
|
||||||
cy.get<Room>("@bobsRoom").then((room) => {
|
cy.get<Room>("@bobsRoom").then((room) => {
|
||||||
|
@ -72,28 +74,30 @@ const testMessages = function(this: CryptoTestContext) {
|
||||||
.should("not.have.descendants", ".mx_EventTile_e2eIcon_warning");
|
.should("not.have.descendants", ".mx_EventTile_e2eIcon_warning");
|
||||||
};
|
};
|
||||||
|
|
||||||
const bobJoin = function(this: CryptoTestContext) {
|
const bobJoin = function (this: CryptoTestContext) {
|
||||||
cy.window({ log: false }).then(async win => {
|
cy.window({ log: false })
|
||||||
const bobRooms = this.bob.getRooms();
|
.then(async (win) => {
|
||||||
if (!bobRooms.length) {
|
const bobRooms = this.bob.getRooms();
|
||||||
await new Promise<void>(resolve => {
|
if (!bobRooms.length) {
|
||||||
const onMembership = (_event) => {
|
await new Promise<void>((resolve) => {
|
||||||
this.bob.off(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
const onMembership = (_event) => {
|
||||||
resolve();
|
this.bob.off(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
||||||
};
|
resolve();
|
||||||
this.bob.on(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
};
|
||||||
});
|
this.bob.on(win.matrixcs.RoomMemberEvent.Membership, onMembership);
|
||||||
}
|
});
|
||||||
}).then(() => {
|
}
|
||||||
cy.botJoinRoomByName(this.bob, "Alice").as("bobsRoom");
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
cy.botJoinRoomByName(this.bob, "Alice").as("bobsRoom");
|
||||||
|
});
|
||||||
|
|
||||||
cy.contains(".mx_TextualEvent", "Bob joined the room").should("exist");
|
cy.contains(".mx_TextualEvent", "Bob joined the room").should("exist");
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 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,21 +107,23 @@ 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(
|
||||||
const onShowSas = (event: ISasEvent) => {
|
new Promise<EmojiMapping[]>((resolve) => {
|
||||||
verifier.off("show_sas", onShowSas);
|
const onShowSas = (event: ISasEvent) => {
|
||||||
event.confirm();
|
verifier.off("show_sas", onShowSas);
|
||||||
verifier.done();
|
event.confirm();
|
||||||
resolve(event.sas.emoji);
|
verifier.done();
|
||||||
};
|
resolve(event.sas.emoji);
|
||||||
|
};
|
||||||
|
|
||||||
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) {
|
||||||
const bobsVerificationRequestPromise = waitForVerificationRequest(this.bob);
|
const bobsVerificationRequestPromise = waitForVerificationRequest(this.bob);
|
||||||
|
|
||||||
openRoomInfo().within(() => {
|
openRoomInfo().within(() => {
|
||||||
|
@ -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)
|
||||||
verificationRequest.accept();
|
.then((verificationRequest: VerificationRequest) => {
|
||||||
return verificationRequest;
|
verificationRequest.accept();
|
||||||
}).as("bobsVerificationRequest");
|
return verificationRequest;
|
||||||
|
})
|
||||||
|
.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]);
|
||||||
});
|
});
|
||||||
|
@ -145,15 +153,17 @@ 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")
|
||||||
cy.initTestUser(synapse, "Alice");
|
.as("synapse")
|
||||||
cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false }).as("bob");
|
.then((synapse: SynapseInstance) => {
|
||||||
});
|
cy.initTestUser(synapse, "Alice");
|
||||||
|
cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false }).as("bob");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function(this: CryptoTestContext) {
|
afterEach(function (this: CryptoTestContext) {
|
||||||
cy.stopSynapse(this.synapse);
|
cy.stopSynapse(this.synapse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,27 +182,24 @@ describe("Cryptography", function() {
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creating a DM should work, being e2e-encrypted / user verification", function(this: CryptoTestContext) {
|
it("creating a DM should work, being e2e-encrypted / user verification", function (this: CryptoTestContext) {
|
||||||
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);
|
||||||
verify.call(this);
|
verify.call(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow verification when there is no existing DM", function(this: CryptoTestContext) {
|
it("should allow verification when there is no existing DM", function (this: CryptoTestContext) {
|
||||||
cy.bootstrapCrossSigning();
|
cy.bootstrapCrossSigning();
|
||||||
autoJoin(this.bob);
|
autoJoin(this.bob);
|
||||||
|
|
||||||
/* 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,103 +150,100 @@ 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"),
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.get<{}>("@integrationManager"),
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
]).then(([targetUser, roomId]) => {
|
cy.inviteUser(roomId, targetUserId);
|
||||||
const targetUserId = targetUser.getUserId();
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
|
||||||
cy.inviteUser(roomId, targetUserId);
|
|
||||||
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"),
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.get<{}>("@integrationManager"),
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
]).then(([targetUser, roomId]) => {
|
cy.inviteUser(roomId, targetUserId);
|
||||||
const targetUserId = targetUser.getUserId();
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.getClient()
|
||||||
cy.inviteUser(roomId, targetUserId);
|
.then(async (client) => {
|
||||||
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist');
|
await client.sendStateEvent(roomId, "m.room.power_levels", {
|
||||||
cy.getClient().then(async client => {
|
kick: 50,
|
||||||
await client.sendStateEvent(roomId, 'm.room.power_levels', {
|
users: {
|
||||||
kick: 50,
|
[testUser.userId]: 0,
|
||||||
users: {
|
},
|
||||||
[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"),
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.get<{}>("@integrationManager"),
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
]).then(([targetUser, roomId]) => {
|
cy.inviteUser(roomId, targetUserId);
|
||||||
const targetUserId = targetUser.getUserId();
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`)
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
.should("exist")
|
||||||
cy.inviteUser(roomId, targetUserId);
|
.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"),
|
const targetUserId = targetUser.getUserId();
|
||||||
cy.get<{}>("@integrationManager"),
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
]).then(([targetUser, roomId]) => {
|
cy.inviteUser(roomId, targetUserId);
|
||||||
const targetUserId = targetUser.getUserId();
|
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
cy.getClient()
|
||||||
cy.inviteUser(roomId, targetUserId);
|
.then(async (client) => {
|
||||||
cy.contains(`${BOT_DISPLAY_NAME} joined the room`).should('exist');
|
await client.ban(roomId, targetUserId);
|
||||||
cy.getClient().then(async client => {
|
})
|
||||||
await client.ban(roomId, targetUserId);
|
.then(() => {
|
||||||
}).then(() => {
|
openIntegrationManager();
|
||||||
|
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
||||||
|
closeIntegrationManager(integrationManagerUrl);
|
||||||
|
expectKickedMessage(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should no-op if the target was never a room member", () => {
|
||||||
|
cy.all([cy.get<MatrixClient>("@bob"), cy.get<string>("@roomId"), cy.get<{}>("@integrationManager")]).then(
|
||||||
|
([targetUser, roomId]) => {
|
||||||
|
const targetUserId = targetUser.getUserId();
|
||||||
|
cy.viewRoomByName(ROOM_NAME);
|
||||||
|
|
||||||
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", () => {
|
|
||||||
cy.all([
|
|
||||||
cy.get<MatrixClient>("@bob"),
|
|
||||||
cy.get<string>("@roomId"),
|
|
||||||
cy.get<{}>("@integrationManager"),
|
|
||||||
]).then(([targetUser, roomId]) => {
|
|
||||||
const targetUserId = targetUser.getUserId();
|
|
||||||
cy.viewRoomByName(ROOM_NAME);
|
|
||||||
|
|
||||||
openIntegrationManager();
|
|
||||||
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
|
||||||
closeIntegrationManager(integrationManagerUrl);
|
|
||||||
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
|
||||||
name,
|
.wrap(
|
||||||
room_alias_name: "lltest",
|
bob
|
||||||
visibility: win.matrixcs.Visibility.Public,
|
.createRoom({
|
||||||
}).then(r => r.room_id), { log: false }).as("roomId");
|
name,
|
||||||
|
room_alias_name: "lltest",
|
||||||
|
visibility: win.matrixcs.Visibility.Public,
|
||||||
|
})
|
||||||
|
.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"]')
|
||||||
const pollVote = PollResponseEvent.from([optionId], pollId).serialize();
|
.invoke("attr", "value")
|
||||||
bot.sendEvent(
|
.then((optionId) => {
|
||||||
roomId,
|
const pollVote = PollResponseEvent.from([optionId], pollId).serialize();
|
||||||
pollVote.type,
|
bot.sendEvent(roomId, pollVote.type, pollVote.content);
|
||||||
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,34 +43,35 @@ 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_MessageComposer_sendMessage").click();
|
cy.get(".mx_Autocomplete_Completion_title").click();
|
||||||
|
cy.get(".mx_MessageComposer_sendMessage").click();
|
||||||
|
|
||||||
// find the pill in the timeline and click it
|
// find the pill in the timeline and click it
|
||||||
cy.get(".mx_EventTile_body .mx_Pill").click();
|
cy.get(".mx_EventTile_body .mx_Pill").click();
|
||||||
|
|
||||||
const localUrl = `/#/room/#${targetLocalpart}:`;
|
const localUrl = `/#/room/#${targetLocalpart}:`;
|
||||||
// verify we landed at a sane place
|
// verify we landed at a sane place
|
||||||
cy.url().should("contain", localUrl);
|
cy.url().should("contain", localUrl);
|
||||||
|
|
||||||
cy.wait(250); // let the room list settle
|
cy.wait(250); // let the room list settle
|
||||||
|
|
||||||
// go back to the message room and try to click on the pill text, as a user would
|
// go back to the message room and try to click on the pill text, as a user would
|
||||||
cy.viewRoomByName(messageRoom);
|
cy.viewRoomByName(messageRoom);
|
||||||
cy.get(".mx_EventTile_body .mx_Pill .mx_Pill_linkText")
|
cy.get(".mx_EventTile_body .mx_Pill .mx_Pill_linkText")
|
||||||
.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,17 +25,20 @@ 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")
|
||||||
user = credentials;
|
.then((credentials) => {
|
||||||
}).then(() => {
|
user = credentials;
|
||||||
// create some extra sessions to manage
|
})
|
||||||
return cy.loginUser(synapse, user.username, user.password);
|
.then(() => {
|
||||||
}).then(() => {
|
// create some extra sessions to manage
|
||||||
return cy.loginUser(synapse, user.username, user.password);
|
return cy.loginUser(synapse, user.username, user.password);
|
||||||
});
|
})
|
||||||
|
.then(() => {
|
||||||
|
return cy.loginUser(synapse, user.username, user.password);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -45,72 +48,74 @@ 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")
|
||||||
cy.get('[aria-label="Show details"]').click();
|
.first()
|
||||||
|
.within(() => {
|
||||||
|
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);
|
||||||
cy.get('[data-testid="device-rename-submit-cta"]').click();
|
cy.get('[data-testid="device-rename-submit-cta"]').click();
|
||||||
// there should be a spinner while device updates
|
// there should be a spinner while device updates
|
||||||
cy.get(".mx_Spinner").should("exist");
|
cy.get(".mx_Spinner").should("exist");
|
||||||
// wait for spinner to complete
|
// wait for spinner to complete
|
||||||
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();
|
||||||
});
|
});
|
||||||
// confirm the signout
|
// confirm the signout
|
||||||
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")
|
||||||
cy.startProxy(synapse).as("proxy");
|
.as("synapse")
|
||||||
});
|
.then((synapse) => {
|
||||||
|
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,11 +61,16 @@ 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")
|
||||||
return e.textContent;
|
.should((elements) => {
|
||||||
}), "rooms are sorted").to.deep.equal(wantOrder);
|
expect(
|
||||||
});
|
_.map(elements, (e) => {
|
||||||
|
return e.textContent;
|
||||||
|
}),
|
||||||
|
"rooms are sorted",
|
||||||
|
).to.deep.equal(wantOrder);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const bumpRoom = (alias: string) => {
|
const bumpRoom = (alias: string) => {
|
||||||
// Send a message into the given room, this should bump the room to the top
|
// Send a message into the given room, this should bump the room to the top
|
||||||
|
@ -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
|
||||||
displayName: "Bob",
|
.getBot(synapse, {
|
||||||
}).as("bob");
|
displayName: "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,50 +263,56 @@ 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;
|
||||||
bobClient = bob;
|
let roomRescind;
|
||||||
return Promise.all([
|
let bobClient;
|
||||||
bob.createRoom({ name: "Join" }),
|
cy.get<MatrixClient>("@bob")
|
||||||
bob.createRoom({ name: "Reject" }),
|
.then((bob) => {
|
||||||
bob.createRoom({ name: "Rescind" }),
|
bobClient = bob;
|
||||||
]);
|
return Promise.all([
|
||||||
}).then(([join, reject, rescind]) => {
|
bob.createRoom({ name: "Join" }),
|
||||||
roomJoin = join.room_id;
|
bob.createRoom({ name: "Reject" }),
|
||||||
roomReject = reject.room_id;
|
bob.createRoom({ name: "Rescind" }),
|
||||||
roomRescind = rescind.room_id;
|
]);
|
||||||
return Promise.all([
|
})
|
||||||
bobClient.invite(roomJoin, clientUserId),
|
.then(([join, reject, rescind]) => {
|
||||||
bobClient.invite(roomReject, clientUserId),
|
roomJoin = join.room_id;
|
||||||
bobClient.invite(roomRescind, clientUserId),
|
roomReject = reject.room_id;
|
||||||
]);
|
roomRescind = rescind.room_id;
|
||||||
});
|
return Promise.all([
|
||||||
|
bobClient.invite(roomJoin, clientUserId),
|
||||||
|
bobClient.invite(roomReject, clientUserId),
|
||||||
|
bobClient.invite(roomRescind, clientUserId),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
// 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(
|
||||||
return e.textContent;
|
_.map(elements, (e) => {
|
||||||
}), "rooms are sorted").to.deep.equal(["Rescind"]);
|
return e.textContent;
|
||||||
});
|
}),
|
||||||
|
"rooms are sorted",
|
||||||
|
).to.deep.equal(["Rescind"]);
|
||||||
|
});
|
||||||
|
|
||||||
// now rescind the invite
|
// now rescind the invite
|
||||||
cy.get<MatrixClient>("@bob").then((bob) => {
|
cy.get<MatrixClient>("@bob").then((bob) => {
|
||||||
|
@ -314,19 +320,19 @@ 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");
|
||||||
cy.contains('.mx_RoomSublist[aria-label="People"] .mx_RoomTile', "Favourite DM").should("not.exist");
|
cy.contains('.mx_RoomSublist[aria-label="People"] .mx_RoomTile', "Favourite DM").should("not.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,28 +373,31 @@ 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
|
||||||
body: "First message",
|
.sendEvent(roomId, null, "m.room.message", {
|
||||||
msgtype: "m.text",
|
body: "First message",
|
||||||
}).then(() => {
|
|
||||||
return cy.sendEvent(roomId, null, "m.room.message", {
|
|
||||||
body: "Permalink me",
|
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return cy.sendEvent(roomId, null, "m.room.message", {
|
||||||
|
body: "Permalink me",
|
||||||
|
msgtype: "m.text",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
cy.sendEvent(roomId, null, "m.room.message", {
|
||||||
|
body: "Reply to me",
|
||||||
|
msgtype: "m.text",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}).then(() => {
|
|
||||||
cy.sendEvent(roomId, null, "m.room.message", {
|
|
||||||
body: "Reply to me",
|
|
||||||
msgtype: "m.text",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
// select the room
|
// select the room
|
||||||
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",
|
{
|
||||||
content: {
|
type: "m.room.name",
|
||||||
name: spaceName,
|
content: {
|
||||||
|
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,78 +37,86 @@ 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(
|
||||||
filter: Filter | null,
|
"spotlightFilter",
|
||||||
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
(
|
||||||
): Chainable<JQuery<HTMLElement>> => {
|
filter: Filter | null,
|
||||||
let selector: string;
|
options?: Partial<Loggable & Timeoutable & Withinable & Shadow>,
|
||||||
switch (filter) {
|
): Chainable<JQuery<HTMLElement>> => {
|
||||||
case Filter.People:
|
let selector: string;
|
||||||
selector = "#mx_SpotlightDialog_button_startChat";
|
switch (filter) {
|
||||||
break;
|
case Filter.People:
|
||||||
case Filter.PublicRooms:
|
selector = "#mx_SpotlightDialog_button_startChat";
|
||||||
selector = "#mx_SpotlightDialog_button_explorePublicRooms";
|
break;
|
||||||
break;
|
case Filter.PublicRooms:
|
||||||
default:
|
selector = "#mx_SpotlightDialog_button_explorePublicRooms";
|
||||||
selector = ".mx_SpotlightDialog_filter";
|
break;
|
||||||
break;
|
default:
|
||||||
}
|
selector = ".mx_SpotlightDialog_filter";
|
||||||
return cy.get(selector, options).click();
|
break;
|
||||||
});
|
}
|
||||||
|
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(() =>
|
||||||
bot1 = _bot1;
|
cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => {
|
||||||
}),
|
bot1 = _bot1;
|
||||||
).then(() =>
|
}),
|
||||||
cy.getBot(synapse, { displayName: bot2Name }).then(_bot2 => {
|
)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
.then(() =>
|
||||||
bot2 = _bot2;
|
cy.getBot(synapse, { displayName: bot2Name }).then((_bot2) => {
|
||||||
}),
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
).then(() =>
|
bot2 = _bot2;
|
||||||
cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => {
|
}),
|
||||||
cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then(_room1Id => {
|
)
|
||||||
room1Id = _room1Id;
|
.then(() =>
|
||||||
bot1.joinRoom(room1Id);
|
cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => {
|
||||||
cy.visit("/#/room/" + room1Id);
|
cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then((_room1Id) => {
|
||||||
});
|
room1Id = _room1Id;
|
||||||
bot2.createRoom({ name: room2Name, visibility: Visibility.Public })
|
bot1.joinRoom(room1Id);
|
||||||
.then(({ room_id: _room2Id }) => {
|
cy.visit("/#/room/" + room1Id);
|
||||||
room2Id = _room2Id;
|
|
||||||
bot2.invite(room2Id, bot1.getUserId());
|
|
||||||
});
|
});
|
||||||
bot2.createRoom({
|
bot2.createRoom({ name: room2Name, visibility: Visibility.Public }).then(
|
||||||
name: room3Name,
|
({ room_id: _room2Id }) => {
|
||||||
visibility: Visibility.Public, initial_state: [{
|
room2Id = _room2Id;
|
||||||
type: "m.room.history_visibility",
|
bot2.invite(room2Id, bot1.getUserId());
|
||||||
state_key: "",
|
|
||||||
content: {
|
|
||||||
history_visibility: "world_readable",
|
|
||||||
},
|
},
|
||||||
}],
|
);
|
||||||
}).then(({ room_id: _room3Id }) => {
|
bot2.createRoom({
|
||||||
room3Id = _room3Id;
|
name: room3Name,
|
||||||
bot2.invite(room3Id, bot1.getUserId());
|
visibility: Visibility.Public,
|
||||||
});
|
initial_state: [
|
||||||
}),
|
{
|
||||||
).then(() =>
|
type: "m.room.history_visibility",
|
||||||
cy.get('.mx_RoomSublist_skeletonUI').should('not.exist'),
|
state_key: "",
|
||||||
);
|
content: {
|
||||||
|
history_visibility: "world_readable",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).then(({ room_id: _room3Id }) => {
|
||||||
|
room3Id = _room3Id;
|
||||||
|
bot2.invite(room3Id, bot1.getUserId());
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(() => cy.get(".mx_RoomSublist_skeletonUI").should("not.exist"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -216,63 +228,71 @@ describe("Spotlight", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find joined rooms", () => {
|
it("should find joined rooms", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
cy.spotlightSearch().clear().type(room1Name);
|
.within(() => {
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.spotlightSearch().clear().type(room1Name);
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().eq(0).should("contain", room1Name);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).should("contain", room1Name);
|
||||||
cy.url().should("contain", room1Id);
|
cy.spotlightResults().eq(0).click();
|
||||||
}).then(() => {
|
cy.url().should("contain", room1Id);
|
||||||
cy.roomHeaderName().should("contain", room1Name);
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
cy.roomHeaderName().should("contain", room1Name);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find known public rooms", () => {
|
it("should find known public rooms", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
cy.spotlightFilter(Filter.PublicRooms);
|
.within(() => {
|
||||||
cy.spotlightSearch().clear().type(room1Name);
|
cy.spotlightFilter(Filter.PublicRooms);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.spotlightSearch().clear().type(room1Name);
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().eq(0).should("contain", room1Name);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).should("contain", "View");
|
cy.spotlightResults().eq(0).should("contain", room1Name);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).should("contain", "View");
|
||||||
cy.url().should("contain", room1Id);
|
cy.spotlightResults().eq(0).click();
|
||||||
}).then(() => {
|
cy.url().should("contain", room1Id);
|
||||||
cy.roomHeaderName().should("contain", room1Name);
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
cy.roomHeaderName().should("contain", room1Name);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find unknown public rooms", () => {
|
it("should find unknown public rooms", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
cy.spotlightFilter(Filter.PublicRooms);
|
.within(() => {
|
||||||
cy.spotlightSearch().clear().type(room2Name);
|
cy.spotlightFilter(Filter.PublicRooms);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.spotlightSearch().clear().type(room2Name);
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().eq(0).should("contain", room2Name);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).should("contain", "Join");
|
cy.spotlightResults().eq(0).should("contain", room2Name);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).should("contain", "Join");
|
||||||
cy.url().should("contain", room2Id);
|
cy.spotlightResults().eq(0).click();
|
||||||
}).then(() => {
|
cy.url().should("contain", room2Id);
|
||||||
cy.get(".mx_RoomView_MessageList").should("have.length", 1);
|
})
|
||||||
cy.roomHeaderName().should("contain", room2Name);
|
.then(() => {
|
||||||
});
|
cy.get(".mx_RoomView_MessageList").should("have.length", 1);
|
||||||
|
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()
|
||||||
cy.spotlightFilter(Filter.PublicRooms);
|
.within(() => {
|
||||||
cy.spotlightSearch().clear().type(room3Name);
|
cy.spotlightFilter(Filter.PublicRooms);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.spotlightSearch().clear().type(room3Name);
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().eq(0).should("contain", room3Name);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).should("contain", "View");
|
cy.spotlightResults().eq(0).should("contain", room3Name);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).should("contain", "View");
|
||||||
cy.url().should("contain", room3Id);
|
cy.spotlightResults().eq(0).click();
|
||||||
}).then(() => {
|
cy.url().should("contain", room3Id);
|
||||||
cy.get(".mx_RoomPreviewBar_actions .mx_AccessibleButton").click();
|
})
|
||||||
cy.roomHeaderName().should("contain", room3Name);
|
.then(() => {
|
||||||
});
|
cy.get(".mx_RoomPreviewBar_actions .mx_AccessibleButton").click();
|
||||||
|
cy.roomHeaderName().should("contain", room3Name);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: We currently can’t test finding rooms on other homeservers/other protocols
|
// TODO: We currently can’t test finding rooms on other homeservers/other protocols
|
||||||
|
@ -299,29 +319,33 @@ describe("Spotlight", () => {
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
it("should find known people", () => {
|
it("should find known people", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
cy.spotlightFilter(Filter.People);
|
.within(() => {
|
||||||
cy.spotlightSearch().clear().type(bot1Name);
|
cy.spotlightFilter(Filter.People);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.spotlightSearch().clear().type(bot1Name);
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().eq(0).should("contain", bot1Name);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).should("contain", bot1Name);
|
||||||
}).then(() => {
|
cy.spotlightResults().eq(0).click();
|
||||||
cy.roomHeaderName().should("contain", bot1Name);
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
cy.roomHeaderName().should("contain", bot1Name);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find unknown people", () => {
|
it("should find unknown people", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
cy.spotlightFilter(Filter.People);
|
.within(() => {
|
||||||
cy.spotlightSearch().clear().type(bot2Name);
|
cy.spotlightFilter(Filter.People);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.spotlightSearch().clear().type(bot2Name);
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).click();
|
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
||||||
}).then(() => {
|
cy.spotlightResults().eq(0).click();
|
||||||
cy.roomHeaderName().should("contain", bot2Name);
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
cy.roomHeaderName().should("contain", bot2Name);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find group DMs by usernames or user ids", () => {
|
it("should find group DMs by usernames or user ids", () => {
|
||||||
|
@ -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,17 +428,19 @@ describe("Spotlight", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow opening group chat dialog", () => {
|
it("should allow opening group chat dialog", () => {
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.openSpotlightDialog()
|
||||||
cy.spotlightFilter(Filter.People);
|
.within(() => {
|
||||||
cy.spotlightSearch().clear().type(bot2Name);
|
cy.spotlightFilter(Filter.People);
|
||||||
cy.wait(3000); // wait for the dialog code to settle
|
cy.spotlightSearch().clear().type(bot2Name);
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.wait(3000); // wait for the dialog code to settle
|
||||||
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat");
|
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
||||||
cy.get(".mx_SpotlightDialog_startGroupChat").click();
|
cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat");
|
||||||
}).then(() => {
|
cy.get(".mx_SpotlightDialog_startGroupChat").click();
|
||||||
cy.get('[role=dialog]').should("contain", "Direct Messages");
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
cy.get("[role=dialog]").should("contain", "Direct Messages");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should close spotlight after starting a DM", () => {
|
it("should close spotlight after starting a DM", () => {
|
||||||
|
@ -445,38 +468,40 @@ 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.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
|
||||||
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true");
|
||||||
|
});
|
||||||
|
cy.spotlightSearch()
|
||||||
|
.type("{downArrow}")
|
||||||
|
.then(() => {
|
||||||
|
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
|
||||||
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "false");
|
||||||
|
});
|
||||||
|
cy.spotlightSearch()
|
||||||
|
.type("{upArrow}")
|
||||||
|
.then(() => {
|
||||||
|
cy.spotlightResults().eq(0).should("have.attr", "aria-selected", "false");
|
||||||
|
cy.spotlightResults().eq(1).should("have.attr", "aria-selected", "true");
|
||||||
|
});
|
||||||
|
cy.spotlightSearch()
|
||||||
|
.type("{upArrow}")
|
||||||
|
.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.spotlightResults().eq(0)
|
|
||||||
.should("have.attr", "aria-selected", "false");
|
|
||||||
cy.spotlightResults().eq(1)
|
|
||||||
.should("have.attr", "aria-selected", "true");
|
|
||||||
});
|
|
||||||
cy.spotlightSearch().type("{downArrow}").then(() => {
|
|
||||||
cy.spotlightResults().eq(0)
|
|
||||||
.should("have.attr", "aria-selected", "false");
|
|
||||||
cy.spotlightResults().eq(1)
|
|
||||||
.should("have.attr", "aria-selected", "false");
|
|
||||||
});
|
|
||||||
cy.spotlightSearch().type("{upArrow}").then(() => {
|
|
||||||
cy.spotlightResults().eq(0)
|
|
||||||
.should("have.attr", "aria-selected", "false");
|
|
||||||
cy.spotlightResults().eq(1)
|
|
||||||
.should("have.attr", "aria-selected", "true");
|
|
||||||
});
|
|
||||||
cy.spotlightSearch().type("{upArrow}").then(() => {
|
|
||||||
cy.spotlightResults().eq(0)
|
|
||||||
.should("have.attr", "aria-selected", "true");
|
|
||||||
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(
|
||||||
body: "I'm very good thanks",
|
bot
|
||||||
msgtype: "m.text",
|
.sendMessage(roomId, threadId, {
|
||||||
}).then(res => res.event_id)).as("eventId");
|
body: "I'm very good thanks",
|
||||||
|
msgtype: "m.text",
|
||||||
|
})
|
||||||
|
.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,9 +247,12 @@ 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
|
||||||
cy.get(".mx_EventTile .mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle").should("be.visible");
|
cy.get(".mx_EventTile .mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle").should("be.visible");
|
||||||
|
@ -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,9 +45,11 @@ 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()
|
||||||
const url = new URL(href);
|
.should("contain", "updated=" + NEW_VERSION)
|
||||||
expect(url.searchParams.get("updated")).to.equal(NEW_VERSION);
|
.then((href) => {
|
||||||
});
|
const url = new URL(href);
|
||||||
|
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,44 +55,42 @@ 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");
|
widths: [640],
|
||||||
cy.get('[role=dialog]')
|
});
|
||||||
.percySnapshotElement("App download dialog", {
|
|
||||||
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")
|
||||||
const findPeopleAction = cy.contains(".mx_UserOnboardingTask_action", "Find friends");
|
.invoke("val")
|
||||||
expect(findPeopleAction).to.exist;
|
.then((oldProgress) => {
|
||||||
findPeopleAction.click();
|
const findPeopleAction = cy.contains(".mx_UserOnboardingTask_action", "Find friends");
|
||||||
cy.get(".mx_InviteDialog_editor input").type(bot1.getUserId());
|
expect(findPeopleAction).to.exist;
|
||||||
cy.get(".mx_InviteDialog_buttonAndSpinner").click();
|
findPeopleAction.click();
|
||||||
cy.get(".mx_InviteDialog_buttonAndSpinner").should("not.exist");
|
cy.get(".mx_InviteDialog_editor input").type(bot1.getUserId());
|
||||||
const message = "Hi!";
|
cy.get(".mx_InviteDialog_buttonAndSpinner").click();
|
||||||
cy.get(".mx_SendMessageComposer").type(`${message}!{enter}`);
|
cy.get(".mx_InviteDialog_buttonAndSpinner").should("not.exist");
|
||||||
cy.contains(".mx_MTextBody.mx_EventTile_content", message);
|
const message = "Hi!";
|
||||||
cy.visit("/#/home");
|
cy.get(".mx_SendMessageComposer").type(`${message}!{enter}`);
|
||||||
cy.get('.mx_UserOnboardingPage').should('exist');
|
cy.contains(".mx_MTextBody.mx_EventTile_content", message);
|
||||||
cy.get('.mx_UserOnboardingButton').should('exist');
|
cy.visit("/#/home");
|
||||||
cy.get('.mx_UserOnboardingList')
|
cy.get(".mx_UserOnboardingPage").should("exist");
|
||||||
.should('exist')
|
cy.get(".mx_UserOnboardingButton").should("exist");
|
||||||
.should(($list) => {
|
cy.get(".mx_UserOnboardingList")
|
||||||
const list = $list.get(0);
|
.should("exist")
|
||||||
expect(getComputedStyle(list).opacity).to.be.eq("1");
|
.should(($list) => {
|
||||||
});
|
const list = $list.get(0);
|
||||||
cy.get(".mx_ProgressBar").invoke("val").should("be.greaterThan", oldProgress);
|
expect(getComputedStyle(list).opacity).to.be.eq("1");
|
||||||
});
|
});
|
||||||
|
cy.get(".mx_ProgressBar").invoke("val").should("be.greaterThan", oldProgress);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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()
|
||||||
const content: IWidget = {
|
.then(async (matrixClient) => {
|
||||||
id: WIDGET_ID,
|
const content: IWidget = {
|
||||||
creatorUserId: 'somebody',
|
id: WIDGET_ID,
|
||||||
type: 'widget',
|
creatorUserId: "somebody",
|
||||||
name: 'widget',
|
type: "widget",
|
||||||
url: widgetUrl,
|
name: "widget",
|
||||||
};
|
url: widgetUrl,
|
||||||
await matrixClient.sendStateEvent(roomId, 'im.vector.modular.widgets', content, WIDGET_ID);
|
};
|
||||||
}).as('widgetEventSent');
|
await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, WIDGET_ID);
|
||||||
|
})
|
||||||
|
.as("widgetEventSent");
|
||||||
|
|
||||||
// set initial layout
|
// set initial layout
|
||||||
cy.getClient().then(async matrixClient => {
|
cy.getClient()
|
||||||
const content = {
|
.then(async (matrixClient) => {
|
||||||
widgets: {
|
const content = {
|
||||||
[WIDGET_ID]: {
|
widgets: {
|
||||||
container: 'top', index: 1, width: 100, height: 0,
|
[WIDGET_ID]: {
|
||||||
|
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) => {
|
||||||
users: {
|
return matrixClient.sendStateEvent(roomId, "m.room.power_levels", {
|
||||||
[user.userId]: 50,
|
users: {
|
||||||
[bot.getUserId()]: 100,
|
[user.userId]: 50,
|
||||||
},
|
[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()
|
||||||
const content: IWidget = {
|
.then(async (matrixClient) => {
|
||||||
id: DEMO_WIDGET_ID,
|
const content: IWidget = {
|
||||||
creatorUserId: 'somebody',
|
id: DEMO_WIDGET_ID,
|
||||||
type: DEMO_WIDGET_TYPE,
|
creatorUserId: "somebody",
|
||||||
name: DEMO_WIDGET_NAME,
|
type: DEMO_WIDGET_TYPE,
|
||||||
url: demoWidgetUrl,
|
name: DEMO_WIDGET_NAME,
|
||||||
};
|
url: demoWidgetUrl,
|
||||||
await matrixClient.sendStateEvent(roomId, 'im.vector.modular.widgets', content, DEMO_WIDGET_ID);
|
};
|
||||||
}).as('widgetEventSent');
|
await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, DEMO_WIDGET_ID);
|
||||||
|
})
|
||||||
|
.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,21 +149,23 @@ 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")
|
||||||
const userId = user.userId;
|
.should("exist")
|
||||||
if (userRemove == 'leave') {
|
.then(async () => {
|
||||||
cy.getClient().then(async matrixClient => {
|
const userId = user.userId;
|
||||||
await matrixClient.leave(roomId);
|
if (userRemove == "leave") {
|
||||||
});
|
cy.getClient().then(async (matrixClient) => {
|
||||||
} else if (userRemove == 'kick') {
|
await matrixClient.leave(roomId);
|
||||||
await bot.kick(roomId, userId);
|
});
|
||||||
} else if (userRemove == 'ban') {
|
} else if (userRemove == "kick") {
|
||||||
await bot.ban(roomId, userId);
|
await bot.kick(roomId, userId);
|
||||||
}
|
} else if (userRemove == "ban") {
|
||||||
|
await bot.ban(roomId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
// checks that pip window is closed
|
// checks that pip window is closed
|
||||||
cy.get(".mx_LegacyCallView_pip").should("not.exist");
|
cy.get(".mx_LegacyCallView_pip").should("not.exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
{
|
{
|
||||||
"versions": [
|
"versions": [
|
||||||
"r0.0.1",
|
"r0.0.1",
|
||||||
"r0.1.0",
|
"r0.1.0",
|
||||||
"r0.2.0",
|
"r0.2.0",
|
||||||
"r0.3.0",
|
"r0.3.0",
|
||||||
"r0.4.0",
|
"r0.4.0",
|
||||||
"r0.5.0",
|
"r0.5.0",
|
||||||
"r0.6.0",
|
"r0.6.0",
|
||||||
"r0.6.1",
|
"r0.6.1",
|
||||||
"v1.1",
|
"v1.1",
|
||||||
"v1.2",
|
"v1.2",
|
||||||
"v1.3",
|
"v1.3",
|
||||||
"v1.4"
|
"v1.4"
|
||||||
],
|
],
|
||||||
"unstable_features": {
|
"unstable_features": {
|
||||||
"org.matrix.label_based_filtering": true,
|
"org.matrix.label_based_filtering": true,
|
||||||
"org.matrix.e2e_cross_signing": true,
|
"org.matrix.e2e_cross_signing": true,
|
||||||
"org.matrix.msc2432": true,
|
"org.matrix.msc2432": true,
|
||||||
"uk.half-shot.msc2666.mutual_rooms": true,
|
"uk.half-shot.msc2666.mutual_rooms": true,
|
||||||
"io.element.e2ee_forced.public": false,
|
"io.element.e2ee_forced.public": false,
|
||||||
"io.element.e2ee_forced.private": false,
|
"io.element.e2ee_forced.private": false,
|
||||||
"io.element.e2ee_forced.trusted_private": false,
|
"io.element.e2ee_forced.trusted_private": false,
|
||||||
"org.matrix.msc3026.busy_presence": false,
|
"org.matrix.msc3026.busy_presence": false,
|
||||||
"org.matrix.msc2285.stable": true,
|
"org.matrix.msc2285.stable": true,
|
||||||
"org.matrix.msc3827.stable": true,
|
"org.matrix.msc3827.stable": true,
|
||||||
"org.matrix.msc2716": false,
|
"org.matrix.msc2716": false,
|
||||||
"org.matrix.msc3030": false,
|
"org.matrix.msc3030": false,
|
||||||
"org.matrix.msc3440.stable": true,
|
"org.matrix.msc3440.stable": true,
|
||||||
"org.matrix.msc3771": true,
|
"org.matrix.msc3771": true,
|
||||||
"org.matrix.msc3773": false,
|
"org.matrix.msc3773": false,
|
||||||
"fi.mau.msc2815": false,
|
"fi.mau.msc2815": false,
|
||||||
"org.matrix.msc3882": false,
|
"org.matrix.msc3882": false,
|
||||||
"org.matrix.msc3881": false,
|
"org.matrix.msc3881": false,
|
||||||
"org.matrix.msc3874": false,
|
"org.matrix.msc3874": false,
|
||||||
"org.matrix.msc3886": false,
|
"org.matrix.msc3886": false,
|
||||||
"org.matrix.msc3912": false
|
"org.matrix.msc3912": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,23 +59,22 @@ 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" },
|
||||||
if (err) {
|
(err, stdout, stderr) => {
|
||||||
console.log(stdout);
|
if (err) {
|
||||||
console.log(stderr);
|
console.log(stdout);
|
||||||
reject(err);
|
console.log(stderr);
|
||||||
return;
|
reject(err);
|
||||||
}
|
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());
|
},
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { log } from "./log";
|
||||||
/**
|
/**
|
||||||
* @type {Cypress.PluginConfig}
|
* @type {Cypress.PluginConfig}
|
||||||
*/
|
*/
|
||||||
export default function(on: PluginEvents, config: PluginConfigOptions) {
|
export default function (on: PluginEvents, config: PluginConfigOptions) {
|
||||||
docker(on, config);
|
docker(on, config);
|
||||||
synapseDocker(on, config);
|
synapseDocker(on, config);
|
||||||
slidingSyncProxyDocker(on, config);
|
slidingSyncProxyDocker(on, config);
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -5,21 +5,21 @@ pid_file: /data/homeserver.pid
|
||||||
public_baseurl: http://localhost:8008/
|
public_baseurl: http://localhost:8008/
|
||||||
# Listener is always port 8008 (configured in the container)
|
# Listener is always port 8008 (configured in the container)
|
||||||
listeners:
|
listeners:
|
||||||
- port: 8008
|
- port: 8008
|
||||||
tls: false
|
tls: false
|
||||||
bind_addresses: ['::']
|
bind_addresses: ["::"]
|
||||||
type: http
|
type: http
|
||||||
x_forwarded: true
|
x_forwarded: true
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- names: [client, federation, consent]
|
- names: [client, federation, consent]
|
||||||
compress: false
|
compress: false
|
||||||
|
|
||||||
# An sqlite in-memory database is fast & automatically wipes each time
|
# An sqlite in-memory database is fast & automatically wipes each time
|
||||||
database:
|
database:
|
||||||
name: "sqlite3"
|
name: "sqlite3"
|
||||||
args:
|
args:
|
||||||
database: ":memory:"
|
database: ":memory:"
|
||||||
|
|
||||||
# Needs to be configured to log to the console like a good docker process
|
# Needs to be configured to log to the console like a good docker process
|
||||||
log_config: "/data/log.config"
|
log_config: "/data/log.config"
|
||||||
|
@ -27,19 +27,19 @@ log_config: "/data/log.config"
|
||||||
rc_messages_per_second: 10000
|
rc_messages_per_second: 10000
|
||||||
rc_message_burst_count: 10000
|
rc_message_burst_count: 10000
|
||||||
rc_registration:
|
rc_registration:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
rc_login:
|
rc_login:
|
||||||
address:
|
address:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
account:
|
account:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
failed_attempts:
|
failed_attempts:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
media_store_path: "/data/media_store"
|
media_store_path: "/data/media_store"
|
||||||
uploads_path: "/data/uploads"
|
uploads_path: "/data/uploads"
|
||||||
|
@ -54,19 +54,19 @@ form_secret: "{{FORM_SECRET}}"
|
||||||
# Signing key must be here: it will be generated to this file
|
# Signing key must be here: it will be generated to this file
|
||||||
signing_key_path: "/data/localhost.signing.key"
|
signing_key_path: "/data/localhost.signing.key"
|
||||||
email:
|
email:
|
||||||
enable_notifs: false
|
enable_notifs: false
|
||||||
smtp_host: "localhost"
|
smtp_host: "localhost"
|
||||||
smtp_port: 25
|
smtp_port: 25
|
||||||
smtp_user: "exampleusername"
|
smtp_user: "exampleusername"
|
||||||
smtp_pass: "examplepassword"
|
smtp_pass: "examplepassword"
|
||||||
require_transport_security: False
|
require_transport_security: False
|
||||||
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
||||||
app_name: Matrix
|
app_name: Matrix
|
||||||
notif_template_html: notif_mail.html
|
notif_template_html: notif_mail.html
|
||||||
notif_template_text: notif_mail.txt
|
notif_template_text: notif_mail.txt
|
||||||
notif_for_new_users: True
|
notif_for_new_users: True
|
||||||
client_base_url: "http://localhost/element"
|
client_base_url: "http://localhost/element"
|
||||||
|
|
||||||
trusted_key_servers:
|
trusted_key_servers:
|
||||||
- server_name: "matrix.org"
|
- server_name: "matrix.org"
|
||||||
suppress_key_server_warning: true
|
suppress_key_server_warning: true
|
||||||
|
|
|
@ -2,39 +2,39 @@ server_name: "localhost"
|
||||||
pid_file: /data/homeserver.pid
|
pid_file: /data/homeserver.pid
|
||||||
public_baseurl: "{{PUBLIC_BASEURL}}"
|
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
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- names: [client, federation, consent]
|
- names: [client, federation, consent]
|
||||||
compress: false
|
compress: false
|
||||||
|
|
||||||
database:
|
database:
|
||||||
name: "sqlite3"
|
name: "sqlite3"
|
||||||
args:
|
args:
|
||||||
database: ":memory:"
|
database: ":memory:"
|
||||||
|
|
||||||
log_config: "/data/log.config"
|
log_config: "/data/log.config"
|
||||||
|
|
||||||
rc_messages_per_second: 10000
|
rc_messages_per_second: 10000
|
||||||
rc_message_burst_count: 10000
|
rc_message_burst_count: 10000
|
||||||
rc_registration:
|
rc_registration:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
rc_login:
|
rc_login:
|
||||||
address:
|
address:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
account:
|
account:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
failed_attempts:
|
failed_attempts:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
media_store_path: "/data/media_store"
|
media_store_path: "/data/media_store"
|
||||||
uploads_path: "/data/uploads"
|
uploads_path: "/data/uploads"
|
||||||
|
@ -47,38 +47,38 @@ macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
|
||||||
form_secret: "{{FORM_SECRET}}"
|
form_secret: "{{FORM_SECRET}}"
|
||||||
signing_key_path: "/data/localhost.signing.key"
|
signing_key_path: "/data/localhost.signing.key"
|
||||||
email:
|
email:
|
||||||
enable_notifs: false
|
enable_notifs: false
|
||||||
smtp_host: "localhost"
|
smtp_host: "localhost"
|
||||||
smtp_port: 25
|
smtp_port: 25
|
||||||
smtp_user: "exampleusername"
|
smtp_user: "exampleusername"
|
||||||
smtp_pass: "examplepassword"
|
smtp_pass: "examplepassword"
|
||||||
require_transport_security: False
|
require_transport_security: False
|
||||||
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
||||||
app_name: Matrix
|
app_name: Matrix
|
||||||
notif_template_html: notif_mail.html
|
notif_template_html: notif_mail.html
|
||||||
notif_template_text: notif_mail.txt
|
notif_template_text: notif_mail.txt
|
||||||
notif_for_new_users: True
|
notif_for_new_users: True
|
||||||
client_base_url: "http://localhost/element"
|
client_base_url: "http://localhost/element"
|
||||||
|
|
||||||
user_consent:
|
user_consent:
|
||||||
template_dir: /data/res/templates/privacy
|
template_dir: /data/res/templates/privacy
|
||||||
version: 1.0
|
version: 1.0
|
||||||
server_notice_content:
|
server_notice_content:
|
||||||
msgtype: m.text
|
msgtype: m.text
|
||||||
body: >-
|
body: >-
|
||||||
To continue using this homeserver you must review and agree to the
|
To continue using this homeserver you must review and agree to the
|
||||||
terms and conditions at %(consent_uri)s
|
terms and conditions at %(consent_uri)s
|
||||||
send_server_notice_to_guests: True
|
send_server_notice_to_guests: True
|
||||||
block_events_error: >-
|
block_events_error: >-
|
||||||
To continue using this homeserver you must review and agree to the
|
To continue using this homeserver you must review and agree to the
|
||||||
terms and conditions at %(consent_uri)s
|
terms and conditions at %(consent_uri)s
|
||||||
require_at_registration: true
|
require_at_registration: true
|
||||||
|
|
||||||
server_notices:
|
server_notices:
|
||||||
system_mxid_localpart: notices
|
system_mxid_localpart: notices
|
||||||
system_mxid_display_name: "Server Notices"
|
system_mxid_display_name: "Server Notices"
|
||||||
system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ"
|
system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
room_name: "Server Notices"
|
room_name: "Server Notices"
|
||||||
trusted_key_servers:
|
trusted_key_servers:
|
||||||
- server_name: "matrix.org"
|
- server_name: "matrix.org"
|
||||||
suppress_key_server_warning: true
|
suppress_key_server_warning: true
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
<!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.
|
{% else %}
|
||||||
</p>
|
<p>Please accept the license!</p>
|
||||||
{% else %}
|
<form method="post" action="consent">
|
||||||
<p>
|
<input type="hidden" name="v" value="{{version}}" />
|
||||||
Please accept the license!
|
<input type="hidden" name="u" value="{{user}}" />
|
||||||
</p>
|
<input type="hidden" name="h" value="{{userhmac}}" />
|
||||||
<form method="post" action="consent">
|
<input type="submit" value="Sure thing!" />
|
||||||
<input type="hidden" name="v" value="{{version}}"/>
|
</form>
|
||||||
<input type="hidden" name="u" value="{{user}}"/>
|
{% endif %}
|
||||||
<input type="hidden" name="h" value="{{userhmac}}"/>
|
</body>
|
||||||
<input type="submit" value="Sure thing!"/>
|
</html>
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<!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>
|
||||||
<p>Danke schon</p>
|
<p>Danke schon</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,60 +2,60 @@ server_name: "localhost"
|
||||||
pid_file: /data/homeserver.pid
|
pid_file: /data/homeserver.pid
|
||||||
public_baseurl: "{{PUBLIC_BASEURL}}"
|
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
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- names: [client]
|
- names: [client]
|
||||||
compress: false
|
compress: false
|
||||||
|
|
||||||
database:
|
database:
|
||||||
name: "sqlite3"
|
name: "sqlite3"
|
||||||
args:
|
args:
|
||||||
database: ":memory:"
|
database: ":memory:"
|
||||||
|
|
||||||
log_config: "/data/log.config"
|
log_config: "/data/log.config"
|
||||||
|
|
||||||
rc_messages_per_second: 10000
|
rc_messages_per_second: 10000
|
||||||
rc_message_burst_count: 10000
|
rc_message_burst_count: 10000
|
||||||
rc_registration:
|
rc_registration:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
rc_joins:
|
rc_joins:
|
||||||
local:
|
local:
|
||||||
per_second: 9999
|
per_second: 9999
|
||||||
burst_count: 9999
|
burst_count: 9999
|
||||||
remote:
|
remote:
|
||||||
per_second: 9999
|
per_second: 9999
|
||||||
burst_count: 9999
|
burst_count: 9999
|
||||||
rc_joins_per_room:
|
rc_joins_per_room:
|
||||||
per_second: 9999
|
per_second: 9999
|
||||||
burst_count: 9999
|
burst_count: 9999
|
||||||
rc_3pid_validation:
|
rc_3pid_validation:
|
||||||
per_second: 1000
|
per_second: 1000
|
||||||
burst_count: 1000
|
burst_count: 1000
|
||||||
|
|
||||||
rc_invites:
|
rc_invites:
|
||||||
per_room:
|
per_room:
|
||||||
per_second: 1000
|
per_second: 1000
|
||||||
burst_count: 1000
|
burst_count: 1000
|
||||||
per_user:
|
per_user:
|
||||||
per_second: 1000
|
per_second: 1000
|
||||||
burst_count: 1000
|
burst_count: 1000
|
||||||
|
|
||||||
rc_login:
|
rc_login:
|
||||||
address:
|
address:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
account:
|
account:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
failed_attempts:
|
failed_attempts:
|
||||||
per_second: 10000
|
per_second: 10000
|
||||||
burst_count: 10000
|
burst_count: 10000
|
||||||
|
|
||||||
media_store_path: "/data/media_store"
|
media_store_path: "/data/media_store"
|
||||||
uploads_path: "/data/uploads"
|
uploads_path: "/data/uploads"
|
||||||
|
@ -69,8 +69,8 @@ form_secret: "{{FORM_SECRET}}"
|
||||||
signing_key_path: "/data/localhost.signing.key"
|
signing_key_path: "/data/localhost.signing.key"
|
||||||
|
|
||||||
trusted_key_servers:
|
trusted_key_servers:
|
||||||
- server_name: "matrix.org"
|
- server_name: "matrix.org"
|
||||||
suppress_key_server_warning: true
|
suppress_key_server_warning: true
|
||||||
|
|
||||||
ui_auth:
|
ui_auth:
|
||||||
session_timeout: "300s"
|
session_timeout: "300s"
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -42,4 +42,4 @@ Cypress.Commands.add("tweakConfig", (tweaks: Record<string, any>): Chainable<AUT
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -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(
|
||||||
originalFn: Chainable["checkA11y"],
|
"checkA11y",
|
||||||
context?: string | Node | axe.ContextObject | undefined,
|
(
|
||||||
options: Options = {},
|
originalFn: Chainable["checkA11y"],
|
||||||
violationCallback?: ((violations: axe.Result[]) => void) | undefined,
|
context?: string | Node | axe.ContextObject | undefined,
|
||||||
skipFailures?: boolean,
|
options: Options = {},
|
||||||
): void => {
|
violationCallback?: ((violations: axe.Result[]) => void) | undefined,
|
||||||
return originalFn(context, {
|
skipFailures?: boolean,
|
||||||
...options,
|
): void => {
|
||||||
rules: {
|
return originalFn(
|
||||||
// Disable contrast checking for now as we have too many issues with it
|
context,
|
||||||
'color-contrast': {
|
{
|
||||||
enabled: false,
|
...options,
|
||||||
|
rules: {
|
||||||
|
// Disable contrast checking for now as we have too many issues with it
|
||||||
|
"color-contrast": {
|
||||||
|
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,
|
return cy.getClient().then(async (cli: MatrixClient) => {
|
||||||
content: IContent,
|
return cli.sendEvent(roomId, threadId, eventType, content);
|
||||||
): Chainable<ISendEventResponse> => {
|
});
|
||||||
return cy.getClient().then(async (cli: MatrixClient) => {
|
},
|
||||||
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();
|
||||||
|
@ -54,4 +54,4 @@ Cypress.Commands.add("getClipboardText", (): Chainable<string> => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -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,8 +41,8 @@ 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
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -35,11 +35,15 @@ 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
|
||||||
// Cypress loses types in the mess of wrapping, so force cast
|
.get(selector)
|
||||||
.then(cy.wrap) as Chainable<JQuery<HTMLElement>>;
|
.its("0.contentDocument.body")
|
||||||
|
.should("not.be.empty")
|
||||||
|
// Cypress loses types in the mess of wrapping, so force cast
|
||||||
|
.then(cy.wrap) as Chainable<JQuery<HTMLElement>>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -33,10 +33,13 @@ 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
|
||||||
win.localStorage.setItem(`mx_labs_feature_${feature}`, "true");
|
.window({ log: false })
|
||||||
}).then(() => null);
|
.then((win) => {
|
||||||
|
win.localStorage.setItem(`mx_labs_feature_${feature}`, "true");
|
||||||
|
})
|
||||||
|
.then(() => null);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -49,87 +49,97 @@ 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(
|
||||||
const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
|
"loginUser",
|
||||||
return cy.request<{
|
(synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials> => {
|
||||||
access_token: string;
|
const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
|
||||||
user_id: string;
|
return cy
|
||||||
device_id: string;
|
.request<{
|
||||||
home_server: string;
|
access_token: string;
|
||||||
}>({
|
user_id: string;
|
||||||
url,
|
device_id: string;
|
||||||
method: "POST",
|
home_server: string;
|
||||||
body: {
|
}>({
|
||||||
"type": "m.login.password",
|
url,
|
||||||
"identifier": {
|
method: "POST",
|
||||||
"type": "m.id.user",
|
body: {
|
||||||
"user": username,
|
type: "m.login.password",
|
||||||
|
identifier: {
|
||||||
|
type: "m.id.user",
|
||||||
|
user: username,
|
||||||
|
},
|
||||||
|
password: password,
|
||||||
},
|
},
|
||||||
"password": password,
|
})
|
||||||
},
|
.then((response) => ({
|
||||||
}).then(response => ({
|
password,
|
||||||
password,
|
username,
|
||||||
username,
|
accessToken: response.body.access_token,
|
||||||
accessToken: response.body.access_token,
|
userId: response.body.user_id,
|
||||||
userId: response.body.user_id,
|
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(
|
||||||
// XXX: work around Cypress not clearing IDB between tests
|
"initTestUser",
|
||||||
cy.window({ log: false }).then(win => {
|
(synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable<UserCredentials> => {
|
||||||
win.indexedDB.databases()?.then(databases => {
|
// XXX: work around Cypress not clearing IDB between tests
|
||||||
databases.forEach(database => {
|
cy.window({ log: false }).then((win) => {
|
||||||
win.indexedDB.deleteDatabase(database.name);
|
win.indexedDB.databases()?.then((databases) => {
|
||||||
|
databases.forEach((database) => {
|
||||||
|
win.indexedDB.deleteDatabase(database.name);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
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
|
||||||
return cy.loginUser(synapse, username, password);
|
.registerUser(synapse, username, password, displayName)
|
||||||
}).then(response => {
|
.then(() => {
|
||||||
cy.log(`Registered test user ${username} with displayname ${displayName}`);
|
return cy.loginUser(synapse, username, password);
|
||||||
cy.window({ log: false }).then(win => {
|
})
|
||||||
// Seed the localStorage with the required credentials
|
.then((response) => {
|
||||||
win.localStorage.setItem("mx_hs_url", synapse.baseUrl);
|
cy.log(`Registered test user ${username} with displayname ${displayName}`);
|
||||||
win.localStorage.setItem("mx_user_id", response.userId);
|
cy.window({ log: false }).then((win) => {
|
||||||
win.localStorage.setItem("mx_access_token", response.accessToken);
|
// Seed the localStorage with the required credentials
|
||||||
win.localStorage.setItem("mx_device_id", response.deviceId);
|
win.localStorage.setItem("mx_hs_url", synapse.baseUrl);
|
||||||
win.localStorage.setItem("mx_is_guest", "false");
|
win.localStorage.setItem("mx_user_id", response.userId);
|
||||||
win.localStorage.setItem("mx_has_pickle_key", "false");
|
win.localStorage.setItem("mx_access_token", response.accessToken);
|
||||||
win.localStorage.setItem("mx_has_access_token", "true");
|
win.localStorage.setItem("mx_device_id", response.deviceId);
|
||||||
|
win.localStorage.setItem("mx_is_guest", "false");
|
||||||
|
win.localStorage.setItem("mx_has_pickle_key", "false");
|
||||||
|
win.localStorage.setItem("mx_has_access_token", "true");
|
||||||
|
|
||||||
// Ensure the language is set to a consistent value
|
// Ensure the language is set to a consistent value
|
||||||
win.localStorage.setItem("mx_local_settings", '{"language":"en"}');
|
win.localStorage.setItem("mx_local_settings", '{"language":"en"}');
|
||||||
});
|
});
|
||||||
|
|
||||||
prelaunchFn?.();
|
prelaunchFn?.();
|
||||||
|
|
||||||
return cy.visit("/").then(() => {
|
return cy
|
||||||
// wait for the app to load
|
.visit("/")
|
||||||
return cy.get(".mx_MatrixChat", { timeout: 30000 });
|
.then(() => {
|
||||||
}).then(() => ({
|
// wait for the app to load
|
||||||
password,
|
return cy.get(".mx_MatrixChat", { timeout: 30000 });
|
||||||
username,
|
})
|
||||||
accessToken: response.accessToken,
|
.then(() => ({
|
||||||
userId: response.userId,
|
password,
|
||||||
deviceId: response.deviceId,
|
username,
|
||||||
homeServer: response.homeServer,
|
accessToken: response.accessToken,
|
||||||
}));
|
userId: response.userId,
|
||||||
});
|
deviceId: response.deviceId,
|
||||||
});
|
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(
|
||||||
headers: {
|
"**/_matrix/**",
|
||||||
"Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
{
|
||||||
|
headers: {
|
||||||
|
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(
|
||||||
headers: {
|
"**/_matrix/**",
|
||||||
"Authorization": "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + win.mxMatrixClientPeg.matrixClient.getAccessToken(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, req => {
|
(req) => {
|
||||||
req.continue();
|
req.continue();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
win.dispatchEvent(new Event("online"));
|
win.dispatchEvent(new Event("online"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -85,4 +93,4 @@ Cypress.Commands.add("stubDefaultServer", (): void => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -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,16 +39,16 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -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,
|
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
||||||
value: any,
|
return cy.wrap(store.setValue(name, roomId, level, value));
|
||||||
): Chainable<void> => {
|
});
|
||||||
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
},
|
||||||
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(
|
||||||
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
"getSettingValue",
|
||||||
return store.getValue(name, roomId, excludeDefault);
|
<T = any>(name: string, roomId?: string, excludeDefault?: boolean): Chainable<T> => {
|
||||||
});
|
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
|
||||||
});
|
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,16 +163,22 @@ 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
|
||||||
return cy.get(".mx_BetaCard_buttons").contains("Join the beta").click();
|
.contains(".mx_BetaCard_title", name)
|
||||||
});
|
.closest(".mx_BetaCard")
|
||||||
|
.within(() => {
|
||||||
|
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
|
||||||
return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click();
|
.contains(".mx_BetaCard_title", name)
|
||||||
});
|
.closest(".mx_BetaCard")
|
||||||
|
.within(() => {
|
||||||
|
return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -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,38 +83,42 @@ 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
|
||||||
// get a nonce
|
.then(() => {
|
||||||
return cy.request<{ nonce: string }>({ url });
|
// get a nonce
|
||||||
}).then(response => {
|
return cy.request<{ nonce: string }>({ url });
|
||||||
const { nonce } = response.body;
|
})
|
||||||
const mac = crypto.createHmac('sha1', synapse.registrationSecret).update(
|
.then((response) => {
|
||||||
`${nonce}\0${username}\0${password}\0notadmin`,
|
const { nonce } = response.body;
|
||||||
).digest('hex');
|
const mac = crypto
|
||||||
|
.createHmac("sha1", synapse.registrationSecret)
|
||||||
|
.update(`${nonce}\0${username}\0${password}\0notadmin`)
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
return cy.request<{
|
return cy.request<{
|
||||||
access_token: string;
|
access_token: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
home_server: string;
|
home_server: string;
|
||||||
device_id: string;
|
device_id: string;
|
||||||
}>({
|
}>({
|
||||||
url,
|
url,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
nonce,
|
nonce,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
mac,
|
mac,
|
||||||
admin: false,
|
admin: false,
|
||||||
displayname: displayName,
|
displayname: displayName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}).then(response => ({
|
})
|
||||||
homeServer: response.body.home_server,
|
.then((response) => ({
|
||||||
accessToken: response.body.access_token,
|
homeServer: response.body.home_server,
|
||||||
userId: response.body.user_id,
|
accessToken: response.body.access_token,
|
||||||
deviceId: response.body.device_id,
|
userId: response.body.user_id,
|
||||||
}));
|
deviceId: response.body.device_id,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("startSynapse", startSynapse);
|
Cypress.Commands.add("startSynapse", startSynapse);
|
||||||
|
|
|
@ -38,17 +38,19 @@ 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")
|
||||||
if (ref.scrollTop() > 0) {
|
.scrollTo("top", { duration: 100 })
|
||||||
return cy.scrollToTop();
|
.then((ref) => {
|
||||||
}
|
if (ref.scrollTop() > 0) {
|
||||||
});
|
return cy.scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
|
@ -65,4 +67,4 @@ Cypress.Commands.add("findEventTile", (sender: string, body: string): Chainable<
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -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,16 +59,16 @@ 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(), {
|
||||||
attributes: { chainerId: cmd.chainerId },
|
attributes: { chainerId: cmd.chainerId },
|
||||||
}).attributes;
|
}).attributes;
|
||||||
})
|
})
|
||||||
.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(),
|
||||||
|
@ -79,4 +79,4 @@ cy.all = function all(commands): Cypress.Chainable {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -70,4 +70,4 @@ Cypress.Commands.add("viewSpaceHomeByName", (name: string): Chainable<JQuery<HTM
|
||||||
});
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -49,4 +49,4 @@ Cypress.Commands.add("serveHtmlFile", serveHtmlFile);
|
||||||
Cypress.Commands.add("stopWebServers", stopWebServers);
|
Cypress.Commands.add("stopWebServers", stopWebServers);
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export {};
|
||||||
|
|
|
@ -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,32 +102,38 @@ 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`:
|
|
||||||
Template substitution happens in this file. Template variables are:
|
- `homeserver.yaml`:
|
||||||
* `REGISTRATION_SECRET`: The secret used to register users via the REST API.
|
Template substitution happens in this file. Template variables are:
|
||||||
* `MACAROON_SECRET_KEY`: Generated each time for security
|
- `REGISTRATION_SECRET`: The secret used to register users via the REST API.
|
||||||
* `FORM_SECRET`: Generated each time for security
|
- `MACAROON_SECRET_KEY`: Generated each time for security
|
||||||
* `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at
|
- `FORM_SECRET`: Generated each time for security
|
||||||
* `localhost.signing.key`: A signing key is auto-generated and saved to this file.
|
- `PUBLIC_BASEURL`: The localhost url + port combination the synapse is accessible at
|
||||||
Config templates should not contain a signing key and instead assume that one will exist
|
- `localhost.signing.key`: A signing key is auto-generated and saved to this file.
|
||||||
in this file.
|
Config templates should not contain a signing key and instead assume that one will exist
|
||||||
|
in this file.
|
||||||
|
|
||||||
All other files in the template are copied recursively to `/data/`, so the file `foo.html`
|
All other files in the template are copied recursively to `/data/`, so the file `foo.html`
|
||||||
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,37 +1,38 @@
|
||||||
# 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
|
||||||
- If no matching name is found, a visual bell is shown
|
- If no matching name is found, a visual bell is shown
|
||||||
- @ + a letter opens auto complete for members starting with the given letter
|
- @ + a letter opens auto complete for members starting with the given letter
|
||||||
- When inserting a user pill at the start in the composer, a colon and space is appended to the pill
|
- When inserting a user pill at the start in the composer, a colon and space is appended to the pill
|
||||||
- When inserting a user pill anywhere else in composer, only a space is appended to the pill
|
- When inserting a user pill anywhere else in composer, only a space is appended to the pill
|
||||||
- # + a letter opens auto complete for rooms starting with the given letter
|
- # + a letter opens auto complete for rooms starting with the given letter
|
||||||
- : open auto complete for emoji
|
- : open auto complete for emoji
|
||||||
- Pressing arrow-up/arrow-down while the autocomplete is open navigates between auto complete options
|
- Pressing arrow-up/arrow-down while the autocomplete is open navigates between auto complete options
|
||||||
- Pressing tab while the autocomplete is open goes to the next autocomplete option,
|
- Pressing tab while the autocomplete is open goes to the next autocomplete option,
|
||||||
wrapping around at the end after reverting to the typed text first.
|
wrapping around at the end after reverting to the typed text first.
|
||||||
|
|
||||||
## Formatting
|
## Formatting
|
||||||
|
|
||||||
- When selecting text, a formatting bar appears above the selection.
|
- When selecting text, a formatting bar appears above the selection.
|
||||||
- The formatting bar allows to format the selected test as:
|
- The formatting bar allows to format the selected test as:
|
||||||
bold, italic, strikethrough, a block quote, and a code block (inline if no linebreak is selected).
|
bold, italic, strikethrough, a block quote, and a code block (inline if no linebreak is selected).
|
||||||
- Formatting is applied as markdown syntax.
|
- Formatting is applied as markdown syntax.
|
||||||
- Hitting ctrl/cmd+B also marks the selected text as bold
|
- Hitting ctrl/cmd+B also marks the selected text as bold
|
||||||
- Hitting ctrl/cmd+I also marks the selected text as italic
|
- Hitting ctrl/cmd+I also marks the selected text as italic
|
||||||
- Hitting ctrl/cmd+> also marks the selected text as a blockquote
|
- Hitting ctrl/cmd+> also marks the selected text as a blockquote
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
|
||||||
- When hitting the arrow-up button while having the caret at the start in the composer,
|
- When hitting the arrow-up button while having the caret at the start in the composer,
|
||||||
the last message sent by the syncing user is edited.
|
the last message sent by the syncing user is edited.
|
||||||
- Clicking a display name on an event in the timeline inserts a user pill into the composer
|
- Clicking a display name on an event in the timeline inserts a user pill into the composer
|
||||||
- Emoticons (like :-), >:-), :-/, ...) are replaced by emojis while typing if the relevant setting is enabled
|
- Emoticons (like :-), >:-), :-/, ...) are replaced by emojis while typing if the relevant setting is enabled
|
||||||
- Typing in the composer sends typing notifications in the room
|
- Typing in the composer sends typing notifications in the room
|
||||||
- Pressing ctrl/mod+z and ctrl/mod+y undoes/redoes modifications
|
- Pressing ctrl/mod+z and ctrl/mod+y undoes/redoes modifications
|
||||||
- Pressing shift+enter inserts a line break
|
- Pressing shift+enter inserts a line break
|
||||||
- Pressing enter sends the message.
|
- Pressing enter sends the message.
|
||||||
- Choosing "Quote" in the context menu of an event inserts a quote of the event body in the composer.
|
- Choosing "Quote" in the context menu of an event inserts a quote of the event body in the composer.
|
||||||
- Choosing "Reply" in the context menu of an event shows a preview above the composer to reply to.
|
- Choosing "Reply" in the context menu of an event shows a preview above the composer to reply to.
|
||||||
- Pressing alt+arrow up/arrow down navigates in previously sent messages, putting them in the composer.
|
- Pressing alt+arrow up/arrow down navigates in previously sent messages, putting them in the composer.
|
||||||
|
|
|
@ -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();
|
||||||
|
@ -26,12 +26,12 @@ const onKeyDown = (ev: KeyboardEvent): void => {
|
||||||
handled = false;
|
handled = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handled) {
|
if (handled) {
|
||||||
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,23 +6,25 @@ 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
|
|
||||||
react-sdk.
|
|
||||||
* `parentUrl`: The URL of the parent window. This is also needed for
|
|
||||||
communication back to the react-sdk.
|
|
||||||
|
|
||||||
*Hash/fragment (formatted as a query string)*:
|
- `widgetId`: The ID of the widget. This is needed for communication back to the
|
||||||
* `conferenceDomain`: The domain to connect Jitsi Meet to.
|
react-sdk.
|
||||||
* `conferenceId`: The room or conference ID to connect Jitsi Meet to.
|
- `parentUrl`: The URL of the parent window. This is also needed for
|
||||||
* `isAudioOnly`: Boolean for whether this is a voice-only conference. May not
|
communication back to the react-sdk.
|
||||||
be present, should default to `false`.
|
|
||||||
* `displayName`: The display name of the user viewing the widget. May not
|
_Hash/fragment (formatted as a query string)_:
|
||||||
be present or could be null.
|
|
||||||
* `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May
|
- `conferenceDomain`: The domain to connect Jitsi Meet to.
|
||||||
not be present or could be null.
|
- `conferenceId`: The room or conference ID to connect Jitsi Meet to.
|
||||||
* `userId`: The MXID of the user viewing the widget. May not be present or could
|
- `isAudioOnly`: Boolean for whether this is a voice-only conference. May not
|
||||||
be null.
|
be present, should default to `false`.
|
||||||
|
- `displayName`: The display name of the user viewing the widget. May not
|
||||||
|
be present or could be null.
|
||||||
|
- `avatarUrl`: The HTTP(S) URL for the avatar of the user viewing the widget. May
|
||||||
|
not be present or could be null.
|
||||||
|
- `userId`: The MXID of the user viewing the widget. May not be present or could
|
||||||
|
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
|
||||||
being served. For example, `https://develop.element.io/jitsi.html` or `vector://webapp/jitsi.html`.
|
being served. For example, `https://develop.element.io/jitsi.html` or `vector://webapp/jitsi.html`.
|
||||||
|
|
|
@ -10,7 +10,7 @@ implementation, such as the `RoomEchoChamber` (which handles echoable details of
|
||||||
|
|
||||||
Anything that can be locally echoed will be provided by the `GenericEchoChamber` implementation.
|
Anything that can be locally echoed will be provided by the `GenericEchoChamber` implementation.
|
||||||
The echo chamber will also need to deal with external changes, and has full control over whether
|
The echo chamber will also need to deal with external changes, and has full control over whether
|
||||||
or not something has successfully been echoed.
|
or not something has successfully been echoed.
|
||||||
|
|
||||||
An `EchoContext` is provided to echo chambers (usually with a matching type: `RoomEchoContext`
|
An `EchoContext` is provided to echo chambers (usually with a matching type: `RoomEchoContext`
|
||||||
gets provided to a `RoomEchoChamber` for example) with details about their intended area of
|
gets provided to a `RoomEchoChamber` for example) with details about their intended area of
|
||||||
|
@ -21,7 +21,7 @@ The `EchoStore` manages echo chamber instances, builds contexts, and is generall
|
||||||
accessible than the `EchoChamber` class. For separation of concerns, and to try and keep things
|
accessible than the `EchoChamber` class. For separation of concerns, and to try and keep things
|
||||||
tidy, this is an intentional design decision.
|
tidy, this is an intentional design decision.
|
||||||
|
|
||||||
**Note**: The local echo stack uses a "whenable" pattern, which is similar to thenables and
|
**Note**: The local echo stack uses a "whenable" pattern, which is similar to thenables and
|
||||||
`EventEmitter`. Whenables are ways of actioning a changing condition without having to deal
|
`EventEmitter`. Whenables are ways of actioning a changing condition without having to deal
|
||||||
with listeners being torn down. Once the reference count of the Whenable causes garbage collection,
|
with listeners being torn down. Once the reference count of the Whenable causes garbage collection,
|
||||||
the Whenable's listeners will also be torn down. This is accelerated by the `IDestroyable` interface
|
the Whenable's listeners will also be torn down. This is accelerated by the `IDestroyable` interface
|
||||||
|
@ -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,14 +68,14 @@ 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
|
||||||
above bold, etc.
|
above bold, etc.
|
||||||
|
|
|
@ -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,13 +56,14 @@ 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) {
|
||||||
const canSetValue = SettingsStore.canSetValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM);
|
const canSetValue = SettingsStore.canSetValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM);
|
||||||
if (canSetValue) {
|
if (canSetValue) {
|
||||||
SettingsStore.setValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM, newValue);
|
SettingsStore.setValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM, newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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,12 +168,11 @@ 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() {
|
||||||
const callback = (settingName, roomId, level, newValAtLevel, newVal) => {
|
const callback = (settingName, roomId, level, newValAtLevel, newVal) => {
|
||||||
this.setState({color: newVal});
|
this.setState({ color: newVal });
|
||||||
};
|
};
|
||||||
this.settingWatcherRef = SettingsStore.watchSetting("roomColor", "!example:matrix.org", callback);
|
this.settingWatcherRef = SettingsStore.watchSetting("roomColor", "!example:matrix.org", callback);
|
||||||
}
|
}
|
||||||
|
@ -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,24 +4,25 @@ 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": {
|
||||||
|
container: "right",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"second-widget-id": {
|
|
||||||
"container": "right"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
As shown, there are two containers possible for widgets. These containers have different behaviour
|
As shown, there are two containers possible for widgets. These containers have different behaviour
|
||||||
and interpret the other options differently.
|
and interpret the other options differently.
|
||||||
|
|
||||||
## `top` container
|
## `top` container
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ therefore fewer messages can be shown).
|
||||||
The `index` for a widget determines which order the widgets show up in from left to right. Widgets
|
The `index` for a widget determines which order the widgets show up in from left to right. Widgets
|
||||||
without an `index` will show up as the rightmost widgets. Tiebreaks (same `index` or multiple defined
|
without an `index` will show up as the rightmost widgets. Tiebreaks (same `index` or multiple defined
|
||||||
without an `index`) are resolved by comparing widget IDs. A maximum of 3 widgets can be in the top
|
without an `index`) are resolved by comparing widget IDs. A maximum of 3 widgets can be in the top
|
||||||
container - any which exceed this will be ignored (placed into the `right` container). Smaller numbers
|
container - any which exceed this will be ignored (placed into the `right` container). Smaller numbers
|
||||||
represent leftmost widgets.
|
represent leftmost widgets.
|
||||||
|
|
||||||
The `width` is relative width within the container in percentage points. This will be clamped to a
|
The `width` is relative width within the container in percentage points. This will be clamped to a
|
||||||
|
@ -43,7 +44,7 @@ attempt to show them at 33% width each.
|
||||||
Note that the client may impose minimum widths on the widgets, such as a 10% minimum to avoid pinning
|
Note that the client may impose minimum widths on the widgets, such as a 10% minimum to avoid pinning
|
||||||
hidden widgets. In general, widgets defined in the 30-70% range each will be free of these restrictions.
|
hidden widgets. In general, widgets defined in the 30-70% range each will be free of these restrictions.
|
||||||
|
|
||||||
The `height` is not in fact applied per-widget but is recorded per-widget for potential future
|
The `height` is not in fact applied per-widget but is recorded per-widget for potential future
|
||||||
capabilities in future containers. The top container will take the tallest `height` and use that for
|
capabilities in future containers. The top container will take the tallest `height` and use that for
|
||||||
the height of the whole container, and thus all widgets in that container. The `height` is relative
|
the height of the whole container, and thus all widgets in that container. The `height` is relative
|
||||||
to the container, like with `width`, meaning that 100% will consume as much space as the client is
|
to the container, like with `width`, meaning that 100% will consume as much space as the client is
|
||||||
|
|
512
package.json
512
package.json
|
@ -1,261 +1,261 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.62.0",
|
"version": "3.62.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/matrix-org/matrix-react-sdk"
|
"url": "https://github.com/matrix-org/matrix-react-sdk"
|
||||||
},
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"files": [
|
|
||||||
"lib",
|
|
||||||
"res",
|
|
||||||
"src",
|
|
||||||
"scripts",
|
|
||||||
"git-revision.txt",
|
|
||||||
"docs",
|
|
||||||
"header",
|
|
||||||
"CHANGELOG.md",
|
|
||||||
"CONTRIBUTING.rst",
|
|
||||||
"LICENSE",
|
|
||||||
"README.md",
|
|
||||||
"package.json",
|
|
||||||
".stylelintrc.js"
|
|
||||||
],
|
|
||||||
"main": "./src/index.ts",
|
|
||||||
"matrix_src_main": "./src/index.ts",
|
|
||||||
"matrix_lib_main": "./lib/index.ts",
|
|
||||||
"matrix_lib_typings": "./lib/index.d.ts",
|
|
||||||
"matrix_i18n_extra_translation_funcs": [
|
|
||||||
"newTranslatableError"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"prepublishOnly": "yarn build",
|
|
||||||
"i18n": "matrix-gen-i18n",
|
|
||||||
"prunei18n": "matrix-prune-i18n",
|
|
||||||
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
|
||||||
"make-component": "node scripts/make-react-component.js",
|
|
||||||
"rethemendex": "res/css/rethemendex.sh",
|
|
||||||
"clean": "rimraf lib",
|
|
||||||
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
|
|
||||||
"build:compile": "babel -d lib --verbose --extensions \".ts,.js,.tsx\" src",
|
|
||||||
"build:types": "tsc --emitDeclarationOnly --jsx react",
|
|
||||||
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all",
|
|
||||||
"start:all": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:build",
|
|
||||||
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
|
||||||
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
|
||||||
"lint:js": "eslint --max-warnings 0 src test cypress && prettier --check .",
|
|
||||||
"lint:js-fix": "prettier --loglevel=warn --write . && eslint --fix src test cypress",
|
|
||||||
"lint:types": "tsc --noEmit --jsx react && tsc --noEmit --jsx react -p cypress",
|
|
||||||
"lint:style": "stylelint \"res/css/**/*.pcss\"",
|
|
||||||
"test": "jest",
|
|
||||||
"test:cypress": "cypress run",
|
|
||||||
"test:cypress:open": "cypress open",
|
|
||||||
"coverage": "yarn test --coverage"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.12.5",
|
|
||||||
"@matrix-org/analytics-events": "^0.3.0",
|
|
||||||
"@matrix-org/matrix-wysiwyg": "^0.9.0",
|
|
||||||
"@matrix-org/react-sdk-module-api": "^0.0.3",
|
|
||||||
"@sentry/browser": "^7.0.0",
|
|
||||||
"@sentry/tracing": "^7.0.0",
|
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
|
||||||
"await-lock": "^2.1.0",
|
|
||||||
"blurhash": "^1.1.3",
|
|
||||||
"cheerio": "^1.0.0-rc.9",
|
|
||||||
"classnames": "^2.2.6",
|
|
||||||
"commonmark": "^0.30.0",
|
|
||||||
"counterpart": "^0.18.6",
|
|
||||||
"diff-dom": "^4.2.2",
|
|
||||||
"diff-match-patch": "^1.0.5",
|
|
||||||
"emojibase": "6.1.0",
|
|
||||||
"emojibase-data": "7.0.1",
|
|
||||||
"emojibase-regex": "6.0.1",
|
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"filesize": "10.0.5",
|
|
||||||
"flux": "4.0.3",
|
|
||||||
"focus-visible": "^5.2.0",
|
|
||||||
"gfm.css": "^1.1.2",
|
|
||||||
"glob-to-regexp": "^0.4.1",
|
|
||||||
"highlight.js": "^11.3.1",
|
|
||||||
"html-entities": "^2.0.0",
|
|
||||||
"is-ip": "^3.1.0",
|
|
||||||
"jszip": "^3.7.0",
|
|
||||||
"katex": "^0.16.0",
|
|
||||||
"linkify-element": "4.0.0-beta.4",
|
|
||||||
"linkify-string": "4.0.0-beta.4",
|
|
||||||
"linkifyjs": "4.0.0-beta.4",
|
|
||||||
"lodash": "^4.17.20",
|
|
||||||
"maplibre-gl": "^1.15.2",
|
|
||||||
"matrix-encrypt-attachment": "^1.0.3",
|
|
||||||
"matrix-events-sdk": "0.0.1",
|
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
|
||||||
"matrix-widget-api": "^1.1.1",
|
|
||||||
"minimist": "^1.2.5",
|
|
||||||
"opus-recorder": "^8.0.3",
|
|
||||||
"pako": "^2.0.3",
|
|
||||||
"parse5": "^6.0.1",
|
|
||||||
"png-chunks-extract": "^1.0.0",
|
|
||||||
"posthog-js": "1.36.0",
|
|
||||||
"qrcode": "1.5.1",
|
|
||||||
"re-resizable": "^6.9.0",
|
|
||||||
"react": "17.0.2",
|
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
|
||||||
"react-blurhash": "^0.2.0",
|
|
||||||
"react-dom": "17.0.2",
|
|
||||||
"react-focus-lock": "^2.5.1",
|
|
||||||
"react-transition-group": "^4.4.1",
|
|
||||||
"rfc4648": "^1.4.0",
|
|
||||||
"sanitize-filename": "^1.6.3",
|
|
||||||
"sanitize-html": "^2.3.2",
|
|
||||||
"tar-js": "^0.3.0",
|
|
||||||
"ua-parser-js": "^1.0.2",
|
|
||||||
"url": "^0.11.0",
|
|
||||||
"what-input": "^5.2.10",
|
|
||||||
"zxcvbn": "^4.4.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/cli": "^7.12.10",
|
|
||||||
"@babel/core": "^7.12.10",
|
|
||||||
"@babel/eslint-parser": "^7.12.10",
|
|
||||||
"@babel/eslint-plugin": "^7.12.10",
|
|
||||||
"@babel/parser": "^7.12.11",
|
|
||||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
|
||||||
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
|
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.12.10",
|
|
||||||
"@babel/preset-env": "^7.12.11",
|
|
||||||
"@babel/preset-react": "^7.12.10",
|
|
||||||
"@babel/preset-typescript": "^7.12.7",
|
|
||||||
"@babel/register": "^7.12.10",
|
|
||||||
"@babel/traverse": "^7.12.12",
|
|
||||||
"@casualbot/jest-sonar-reporter": "^2.2.5",
|
|
||||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
|
|
||||||
"@peculiar/webcrypto": "^1.4.1",
|
|
||||||
"@percy/cli": "^1.11.0",
|
|
||||||
"@percy/cypress": "^3.1.2",
|
|
||||||
"@sinonjs/fake-timers": "^9.1.2",
|
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
|
||||||
"@testing-library/react": "^12.1.5",
|
|
||||||
"@testing-library/user-event": "^14.4.3",
|
|
||||||
"@types/classnames": "^2.2.11",
|
|
||||||
"@types/commonmark": "^0.27.4",
|
|
||||||
"@types/counterpart": "^0.18.1",
|
|
||||||
"@types/css-font-loading-module": "^0.0.7",
|
|
||||||
"@types/diff-match-patch": "^1.0.32",
|
|
||||||
"@types/enzyme": "^3.10.9",
|
|
||||||
"@types/escape-html": "^1.0.1",
|
|
||||||
"@types/file-saver": "^2.0.3",
|
|
||||||
"@types/flux": "^3.1.9",
|
|
||||||
"@types/fs-extra": "^9.0.13",
|
|
||||||
"@types/geojson": "^7946.0.8",
|
|
||||||
"@types/jest": "^29.2.1",
|
|
||||||
"@types/katex": "^0.14.0",
|
|
||||||
"@types/lodash": "^4.14.168",
|
|
||||||
"@types/modernizr": "^3.5.3",
|
|
||||||
"@types/node": "^16",
|
|
||||||
"@types/pako": "^2.0.0",
|
|
||||||
"@types/parse5": "^6.0.0",
|
|
||||||
"@types/qrcode": "^1.3.5",
|
|
||||||
"@types/react": "17.0.49",
|
|
||||||
"@types/react-beautiful-dnd": "^13.0.0",
|
|
||||||
"@types/react-dom": "17.0.17",
|
|
||||||
"@types/react-test-renderer": "^17.0.1",
|
|
||||||
"@types/react-transition-group": "^4.4.0",
|
|
||||||
"@types/sanitize-html": "^2.3.1",
|
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
|
||||||
"@types/zxcvbn": "^4.4.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
|
||||||
"@typescript-eslint/parser": "^5.6.0",
|
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
|
|
||||||
"allchange": "^1.1.0",
|
|
||||||
"axe-core": "4.4.3",
|
|
||||||
"babel-jest": "^29.0.0",
|
|
||||||
"blob-polyfill": "^7.0.0",
|
|
||||||
"chokidar": "^3.5.1",
|
|
||||||
"cypress": "^11.0.0",
|
|
||||||
"cypress-axe": "^1.0.0",
|
|
||||||
"cypress-real-events": "^1.7.1",
|
|
||||||
"enzyme": "^3.11.0",
|
|
||||||
"enzyme-to-json": "^3.6.2",
|
|
||||||
"eslint": "8.28.0",
|
|
||||||
"eslint-config-google": "^0.14.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-plugin-deprecate": "^0.7.0",
|
|
||||||
"eslint-plugin-import": "^2.25.4",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
|
||||||
"eslint-plugin-matrix-org": "0.9.0",
|
|
||||||
"eslint-plugin-react": "^7.28.0",
|
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
|
||||||
"eslint-plugin-unicorn": "^45.0.0",
|
|
||||||
"fetch-mock-jest": "^1.5.1",
|
|
||||||
"fs-extra": "^11.0.0",
|
|
||||||
"glob": "^8.0.0",
|
|
||||||
"jest": "^29.2.2",
|
|
||||||
"jest-canvas-mock": "^2.3.0",
|
|
||||||
"jest-environment-jsdom": "^29.2.2",
|
|
||||||
"jest-mock": "^29.2.2",
|
|
||||||
"jest-raw-loader": "^1.0.1",
|
|
||||||
"matrix-mock-request": "^2.5.0",
|
|
||||||
"matrix-web-i18n": "^1.3.0",
|
|
||||||
"node-fetch": "2",
|
|
||||||
"postcss-scss": "^4.0.4",
|
|
||||||
"prettier": "2.8.0",
|
|
||||||
"raw-loader": "^4.0.2",
|
|
||||||
"react-test-renderer": "^17.0.2",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"stylelint": "^14.9.1",
|
|
||||||
"stylelint-config-prettier": "^9.0.4",
|
|
||||||
"stylelint-config-standard": "^29.0.0",
|
|
||||||
"stylelint-scss": "^4.2.0",
|
|
||||||
"typescript": "4.9.3",
|
|
||||||
"walk": "^2.3.14"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"snapshotSerializers": [
|
|
||||||
"enzyme-to-json/serializer"
|
|
||||||
],
|
|
||||||
"testEnvironment": "jsdom",
|
|
||||||
"testMatch": [
|
|
||||||
"<rootDir>/test/**/*-test.[jt]s?(x)"
|
|
||||||
],
|
|
||||||
"globalSetup": "<rootDir>/test/globalSetup.js",
|
|
||||||
"setupFiles": [
|
|
||||||
"jest-canvas-mock"
|
|
||||||
],
|
|
||||||
"setupFilesAfterEnv": [
|
|
||||||
"<rootDir>/test/setupTests.js"
|
|
||||||
],
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"\\.(gif|png|ttf|woff2)$": "<rootDir>/__mocks__/imageMock.js",
|
|
||||||
"\\.svg$": "<rootDir>/__mocks__/svg.js",
|
|
||||||
"\\$webapp/i18n/languages.json": "<rootDir>/__mocks__/languages.json",
|
|
||||||
"decoderWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
|
||||||
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
|
|
||||||
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
|
||||||
"workers/(.+)\\.worker\\.ts": "<rootDir>/__mocks__/workerMock.js",
|
|
||||||
"^!!raw-loader!.*": "jest-raw-loader",
|
|
||||||
"RecorderWorklet": "<rootDir>/__mocks__/empty.js"
|
|
||||||
},
|
},
|
||||||
"transformIgnorePatterns": [
|
"license": "Apache-2.0",
|
||||||
"/node_modules/(?!matrix-js-sdk).+$"
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"res",
|
||||||
|
"src",
|
||||||
|
"scripts",
|
||||||
|
"git-revision.txt",
|
||||||
|
"docs",
|
||||||
|
"header",
|
||||||
|
"CHANGELOG.md",
|
||||||
|
"CONTRIBUTING.rst",
|
||||||
|
"LICENSE",
|
||||||
|
"README.md",
|
||||||
|
"package.json",
|
||||||
|
".stylelintrc.js"
|
||||||
],
|
],
|
||||||
"collectCoverageFrom": [
|
"main": "./src/index.ts",
|
||||||
"<rootDir>/src/**/*.{js,ts,tsx}"
|
"matrix_src_main": "./src/index.ts",
|
||||||
|
"matrix_lib_main": "./lib/index.ts",
|
||||||
|
"matrix_lib_typings": "./lib/index.d.ts",
|
||||||
|
"matrix_i18n_extra_translation_funcs": [
|
||||||
|
"newTranslatableError"
|
||||||
],
|
],
|
||||||
"coverageReporters": [
|
"scripts": {
|
||||||
"text-summary",
|
"prepublishOnly": "yarn build",
|
||||||
"lcov"
|
"i18n": "matrix-gen-i18n",
|
||||||
],
|
"prunei18n": "matrix-prune-i18n",
|
||||||
"testResultsProcessor": "@casualbot/jest-sonar-reporter"
|
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
||||||
},
|
"make-component": "node scripts/make-react-component.js",
|
||||||
"@casualbot/jest-sonar-reporter": {
|
"rethemendex": "res/css/rethemendex.sh",
|
||||||
"outputDirectory": "coverage",
|
"clean": "rimraf lib",
|
||||||
"outputName": "jest-sonar-report.xml",
|
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
|
||||||
"relativePaths": true
|
"build:compile": "babel -d lib --verbose --extensions \".ts,.js,.tsx\" src",
|
||||||
}
|
"build:types": "tsc --emitDeclarationOnly --jsx react",
|
||||||
|
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all",
|
||||||
|
"start:all": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:build",
|
||||||
|
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
||||||
|
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
||||||
|
"lint:js": "eslint --max-warnings 0 src test cypress && prettier --check .",
|
||||||
|
"lint:js-fix": "prettier --loglevel=warn --write . && eslint --fix src test cypress",
|
||||||
|
"lint:types": "tsc --noEmit --jsx react && tsc --noEmit --jsx react -p cypress",
|
||||||
|
"lint:style": "stylelint \"res/css/**/*.pcss\"",
|
||||||
|
"test": "jest",
|
||||||
|
"test:cypress": "cypress run",
|
||||||
|
"test:cypress:open": "cypress open",
|
||||||
|
"coverage": "yarn test --coverage"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@matrix-org/analytics-events": "^0.3.0",
|
||||||
|
"@matrix-org/matrix-wysiwyg": "^0.9.0",
|
||||||
|
"@matrix-org/react-sdk-module-api": "^0.0.3",
|
||||||
|
"@sentry/browser": "^7.0.0",
|
||||||
|
"@sentry/tracing": "^7.0.0",
|
||||||
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
|
"await-lock": "^2.1.0",
|
||||||
|
"blurhash": "^1.1.3",
|
||||||
|
"cheerio": "^1.0.0-rc.9",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"commonmark": "^0.30.0",
|
||||||
|
"counterpart": "^0.18.6",
|
||||||
|
"diff-dom": "^4.2.2",
|
||||||
|
"diff-match-patch": "^1.0.5",
|
||||||
|
"emojibase": "6.1.0",
|
||||||
|
"emojibase-data": "7.0.1",
|
||||||
|
"emojibase-regex": "6.0.1",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"filesize": "10.0.5",
|
||||||
|
"flux": "4.0.3",
|
||||||
|
"focus-visible": "^5.2.0",
|
||||||
|
"gfm.css": "^1.1.2",
|
||||||
|
"glob-to-regexp": "^0.4.1",
|
||||||
|
"highlight.js": "^11.3.1",
|
||||||
|
"html-entities": "^2.0.0",
|
||||||
|
"is-ip": "^3.1.0",
|
||||||
|
"jszip": "^3.7.0",
|
||||||
|
"katex": "^0.16.0",
|
||||||
|
"linkify-element": "4.0.0-beta.4",
|
||||||
|
"linkify-string": "4.0.0-beta.4",
|
||||||
|
"linkifyjs": "4.0.0-beta.4",
|
||||||
|
"lodash": "^4.17.20",
|
||||||
|
"maplibre-gl": "^1.15.2",
|
||||||
|
"matrix-encrypt-attachment": "^1.0.3",
|
||||||
|
"matrix-events-sdk": "0.0.1",
|
||||||
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
|
"matrix-widget-api": "^1.1.1",
|
||||||
|
"minimist": "^1.2.5",
|
||||||
|
"opus-recorder": "^8.0.3",
|
||||||
|
"pako": "^2.0.3",
|
||||||
|
"parse5": "^6.0.1",
|
||||||
|
"png-chunks-extract": "^1.0.0",
|
||||||
|
"posthog-js": "1.36.0",
|
||||||
|
"qrcode": "1.5.1",
|
||||||
|
"re-resizable": "^6.9.0",
|
||||||
|
"react": "17.0.2",
|
||||||
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
|
"react-blurhash": "^0.2.0",
|
||||||
|
"react-dom": "17.0.2",
|
||||||
|
"react-focus-lock": "^2.5.1",
|
||||||
|
"react-transition-group": "^4.4.1",
|
||||||
|
"rfc4648": "^1.4.0",
|
||||||
|
"sanitize-filename": "^1.6.3",
|
||||||
|
"sanitize-html": "^2.3.2",
|
||||||
|
"tar-js": "^0.3.0",
|
||||||
|
"ua-parser-js": "^1.0.2",
|
||||||
|
"url": "^0.11.0",
|
||||||
|
"what-input": "^5.2.10",
|
||||||
|
"zxcvbn": "^4.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.12.10",
|
||||||
|
"@babel/core": "^7.12.10",
|
||||||
|
"@babel/eslint-parser": "^7.12.10",
|
||||||
|
"@babel/eslint-plugin": "^7.12.10",
|
||||||
|
"@babel/parser": "^7.12.11",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||||
|
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
||||||
|
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
|
||||||
|
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.12.10",
|
||||||
|
"@babel/preset-env": "^7.12.11",
|
||||||
|
"@babel/preset-react": "^7.12.10",
|
||||||
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
|
"@babel/register": "^7.12.10",
|
||||||
|
"@babel/traverse": "^7.12.12",
|
||||||
|
"@casualbot/jest-sonar-reporter": "^2.2.5",
|
||||||
|
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
|
||||||
|
"@peculiar/webcrypto": "^1.4.1",
|
||||||
|
"@percy/cli": "^1.11.0",
|
||||||
|
"@percy/cypress": "^3.1.2",
|
||||||
|
"@sinonjs/fake-timers": "^9.1.2",
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^12.1.5",
|
||||||
|
"@testing-library/user-event": "^14.4.3",
|
||||||
|
"@types/classnames": "^2.2.11",
|
||||||
|
"@types/commonmark": "^0.27.4",
|
||||||
|
"@types/counterpart": "^0.18.1",
|
||||||
|
"@types/css-font-loading-module": "^0.0.7",
|
||||||
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
|
"@types/enzyme": "^3.10.9",
|
||||||
|
"@types/escape-html": "^1.0.1",
|
||||||
|
"@types/file-saver": "^2.0.3",
|
||||||
|
"@types/flux": "^3.1.9",
|
||||||
|
"@types/fs-extra": "^9.0.13",
|
||||||
|
"@types/geojson": "^7946.0.8",
|
||||||
|
"@types/jest": "^29.2.1",
|
||||||
|
"@types/katex": "^0.14.0",
|
||||||
|
"@types/lodash": "^4.14.168",
|
||||||
|
"@types/modernizr": "^3.5.3",
|
||||||
|
"@types/node": "^16",
|
||||||
|
"@types/pako": "^2.0.0",
|
||||||
|
"@types/parse5": "^6.0.0",
|
||||||
|
"@types/qrcode": "^1.3.5",
|
||||||
|
"@types/react": "17.0.49",
|
||||||
|
"@types/react-beautiful-dnd": "^13.0.0",
|
||||||
|
"@types/react-dom": "17.0.17",
|
||||||
|
"@types/react-test-renderer": "^17.0.1",
|
||||||
|
"@types/react-transition-group": "^4.4.0",
|
||||||
|
"@types/sanitize-html": "^2.3.1",
|
||||||
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
|
"@types/zxcvbn": "^4.4.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||||
|
"@typescript-eslint/parser": "^5.6.0",
|
||||||
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
|
||||||
|
"allchange": "^1.1.0",
|
||||||
|
"axe-core": "4.4.3",
|
||||||
|
"babel-jest": "^29.0.0",
|
||||||
|
"blob-polyfill": "^7.0.0",
|
||||||
|
"chokidar": "^3.5.1",
|
||||||
|
"cypress": "^11.0.0",
|
||||||
|
"cypress-axe": "^1.0.0",
|
||||||
|
"cypress-real-events": "^1.7.1",
|
||||||
|
"enzyme": "^3.11.0",
|
||||||
|
"enzyme-to-json": "^3.6.2",
|
||||||
|
"eslint": "8.28.0",
|
||||||
|
"eslint-config-google": "^0.14.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-deprecate": "^0.7.0",
|
||||||
|
"eslint-plugin-import": "^2.25.4",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
|
"eslint-plugin-matrix-org": "0.9.0",
|
||||||
|
"eslint-plugin-react": "^7.28.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
|
"eslint-plugin-unicorn": "^45.0.0",
|
||||||
|
"fetch-mock-jest": "^1.5.1",
|
||||||
|
"fs-extra": "^11.0.0",
|
||||||
|
"glob": "^8.0.0",
|
||||||
|
"jest": "^29.2.2",
|
||||||
|
"jest-canvas-mock": "^2.3.0",
|
||||||
|
"jest-environment-jsdom": "^29.2.2",
|
||||||
|
"jest-mock": "^29.2.2",
|
||||||
|
"jest-raw-loader": "^1.0.1",
|
||||||
|
"matrix-mock-request": "^2.5.0",
|
||||||
|
"matrix-web-i18n": "^1.3.0",
|
||||||
|
"node-fetch": "2",
|
||||||
|
"postcss-scss": "^4.0.4",
|
||||||
|
"prettier": "2.8.0",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
|
"react-test-renderer": "^17.0.2",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"stylelint": "^14.9.1",
|
||||||
|
"stylelint-config-prettier": "^9.0.4",
|
||||||
|
"stylelint-config-standard": "^29.0.0",
|
||||||
|
"stylelint-scss": "^4.2.0",
|
||||||
|
"typescript": "4.9.3",
|
||||||
|
"walk": "^2.3.14"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"snapshotSerializers": [
|
||||||
|
"enzyme-to-json/serializer"
|
||||||
|
],
|
||||||
|
"testEnvironment": "jsdom",
|
||||||
|
"testMatch": [
|
||||||
|
"<rootDir>/test/**/*-test.[jt]s?(x)"
|
||||||
|
],
|
||||||
|
"globalSetup": "<rootDir>/test/globalSetup.js",
|
||||||
|
"setupFiles": [
|
||||||
|
"jest-canvas-mock"
|
||||||
|
],
|
||||||
|
"setupFilesAfterEnv": [
|
||||||
|
"<rootDir>/test/setupTests.js"
|
||||||
|
],
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"\\.(gif|png|ttf|woff2)$": "<rootDir>/__mocks__/imageMock.js",
|
||||||
|
"\\.svg$": "<rootDir>/__mocks__/svg.js",
|
||||||
|
"\\$webapp/i18n/languages.json": "<rootDir>/__mocks__/languages.json",
|
||||||
|
"decoderWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
||||||
|
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
|
||||||
|
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
||||||
|
"workers/(.+)\\.worker\\.ts": "<rootDir>/__mocks__/workerMock.js",
|
||||||
|
"^!!raw-loader!.*": "jest-raw-loader",
|
||||||
|
"RecorderWorklet": "<rootDir>/__mocks__/empty.js"
|
||||||
|
},
|
||||||
|
"transformIgnorePatterns": [
|
||||||
|
"/node_modules/(?!matrix-js-sdk).+$"
|
||||||
|
],
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"<rootDir>/src/**/*.{js,ts,tsx}"
|
||||||
|
],
|
||||||
|
"coverageReporters": [
|
||||||
|
"text-summary",
|
||||||
|
"lcov"
|
||||||
|
],
|
||||||
|
"testResultsProcessor": "@casualbot/jest-sonar-reporter"
|
||||||
|
},
|
||||||
|
"@casualbot/jest-sonar-reporter": {
|
||||||
|
"outputDirectory": "coverage",
|
||||||
|
"outputName": "jest-sonar-report.xml",
|
||||||
|
"relativePaths": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
subprojects:
|
subprojects:
|
||||||
matrix-js-sdk:
|
matrix-js-sdk:
|
||||||
includeByDefault: false
|
includeByDefault: false
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue