Compare commits
6 commits
main
...
feature/ca
Author | SHA1 | Date | |
---|---|---|---|
|
118efb6234 | ||
|
5a503a31bb | ||
|
f52dd5e772 | ||
|
829c95dd2e | ||
|
ab4a4a4a2a | ||
|
6fc47dd529 |
196
.drone.yml
|
@ -29,91 +29,26 @@ steps:
|
||||||
# AWS_SECRET_ACCESS_KEY:
|
# AWS_SECRET_ACCESS_KEY:
|
||||||
# from_secret: cache_aws_secret_access_key
|
# from_secret: cache_aws_secret_access_key
|
||||||
# settings:
|
# settings:
|
||||||
# debug: true
|
|
||||||
# restore: true
|
# restore: true
|
||||||
# bucket: kolaente.dev-drone-dependency-cache
|
# bucket: kolaente.dev-drone-dependency-cache
|
||||||
# endpoint: https://s3.fr-par.scw.cloud
|
# endpoint: https://s3.fr-par.scw.cloud
|
||||||
# region: fr-par
|
# region: fr-par
|
||||||
# path_style: true
|
# path_style: true
|
||||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
|
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||||
# mount:
|
# mount:
|
||||||
# - .cache
|
# - '.cache'
|
||||||
|
|
||||||
- name: dependencies
|
- name: dependencies
|
||||||
image: node:18-alpine
|
image: node:16
|
||||||
pull: true
|
pull: true
|
||||||
environment:
|
environment:
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
YARN_CACHE_FOLDER: .cache/yarn/
|
||||||
CYPRESS_CACHE_FOLDER: .cache/cypress
|
CYPRESS_CACHE_FOLDER: .cache/cypress/
|
||||||
commands:
|
commands:
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
- yarn --frozen-lockfile --network-timeout 100000
|
||||||
- pnpm install --fetch-timeout 100000
|
|
||||||
# depends_on:
|
# depends_on:
|
||||||
# - restore-cache
|
# - restore-cache
|
||||||
|
|
||||||
- name: lint
|
|
||||||
image: node:18-alpine
|
|
||||||
pull: true
|
|
||||||
environment:
|
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
|
||||||
commands:
|
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
|
||||||
- pnpm run lint
|
|
||||||
depends_on:
|
|
||||||
- dependencies
|
|
||||||
|
|
||||||
- name: build-prod
|
|
||||||
image: node:18-alpine
|
|
||||||
pull: true
|
|
||||||
environment:
|
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
|
||||||
commands:
|
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
|
||||||
- pnpm run build
|
|
||||||
depends_on:
|
|
||||||
- dependencies
|
|
||||||
|
|
||||||
- name: test-unit
|
|
||||||
image: node:18-alpine
|
|
||||||
pull: true
|
|
||||||
commands:
|
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
|
||||||
- pnpm run test:unit
|
|
||||||
depends_on:
|
|
||||||
- dependencies
|
|
||||||
|
|
||||||
- name: typecheck
|
|
||||||
failure: ignore
|
|
||||||
image: node:18-alpine
|
|
||||||
pull: true
|
|
||||||
environment:
|
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
|
||||||
commands:
|
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
|
||||||
- pnpm run typecheck
|
|
||||||
depends_on:
|
|
||||||
- dependencies
|
|
||||||
|
|
||||||
- name: test-frontend
|
|
||||||
image: cypress/browsers:node16.14.0-chrome99-ff97
|
|
||||||
pull: true
|
|
||||||
environment:
|
|
||||||
CYPRESS_API_URL: http://api:3456/api/v1
|
|
||||||
CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB
|
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
|
||||||
CYPRESS_CACHE_FOLDER: .cache/cypress
|
|
||||||
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
|
|
||||||
CYPRESS_RECORD_KEY:
|
|
||||||
from_secret: cypress_project_key
|
|
||||||
commands:
|
|
||||||
- sed -i 's/localhost/api/g' dist/index.html
|
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
|
||||||
- pnpm cypress install
|
|
||||||
- pnpm run serve:dist & npx wait-on http://localhost:4173
|
|
||||||
- pnpm run test:frontend --browser chrome --record
|
|
||||||
depends_on:
|
|
||||||
- build-prod
|
|
||||||
|
|
||||||
# - name: rebuild-cache
|
# - name: rebuild-cache
|
||||||
# image: meltwater/drone-cache:dev
|
# image: meltwater/drone-cache:dev
|
||||||
# pull: true
|
# pull: true
|
||||||
|
@ -128,14 +63,91 @@ steps:
|
||||||
# endpoint: https://s3.fr-par.scw.cloud
|
# endpoint: https://s3.fr-par.scw.cloud
|
||||||
# region: fr-par
|
# region: fr-par
|
||||||
# path_style: true
|
# path_style: true
|
||||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
|
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||||
# mount:
|
# mount:
|
||||||
# - .cache
|
# - '.cache'
|
||||||
# depends_on:
|
# depends_on:
|
||||||
# - dependencies
|
# - dependencies
|
||||||
|
|
||||||
|
- name: lint
|
||||||
|
image: node:16
|
||||||
|
pull: true
|
||||||
|
environment:
|
||||||
|
YARN_CACHE_FOLDER: .cache/yarn/
|
||||||
|
CYPRESS_CACHE_FOLDER: .cache/cypress/
|
||||||
|
commands:
|
||||||
|
- yarn run lint
|
||||||
|
depends_on:
|
||||||
|
- dependencies
|
||||||
|
|
||||||
|
- name: build-prod
|
||||||
|
image: node:16
|
||||||
|
pull: true
|
||||||
|
environment:
|
||||||
|
YARN_CACHE_FOLDER: .cache/yarn/
|
||||||
|
commands:
|
||||||
|
- yarn build
|
||||||
|
depends_on:
|
||||||
|
- dependencies
|
||||||
|
|
||||||
|
- name: test-unit
|
||||||
|
image: node:16
|
||||||
|
pull: true
|
||||||
|
commands:
|
||||||
|
- yarn test:unit
|
||||||
|
depends_on:
|
||||||
|
- dependencies
|
||||||
|
|
||||||
|
- name: typecheck
|
||||||
|
failure: ignore
|
||||||
|
image: node:16
|
||||||
|
pull: true
|
||||||
|
commands:
|
||||||
|
- yarn typecheck
|
||||||
|
depends_on:
|
||||||
|
- dependencies
|
||||||
|
|
||||||
|
- name: test-frontend
|
||||||
|
image: cypress/browsers:node16.5.0-chrome94-ff93
|
||||||
|
pull: true
|
||||||
|
environment:
|
||||||
|
CYPRESS_API_URL: http://api:3456/api/v1
|
||||||
|
CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB
|
||||||
|
YARN_CACHE_FOLDER: .cache/yarn/
|
||||||
|
CYPRESS_CACHE_FOLDER: .cache/cypress/
|
||||||
|
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
|
||||||
|
commands:
|
||||||
|
- sed -i 's/localhost/api/g' dist/index.html
|
||||||
|
- yarn serve:dist & npx wait-on http://localhost:5000
|
||||||
|
- yarn test:frontend --browser chrome
|
||||||
|
depends_on:
|
||||||
|
- dependencies
|
||||||
|
- build-prod
|
||||||
|
|
||||||
|
- name: upload-test-results
|
||||||
|
image: plugins/s3
|
||||||
|
pull: true
|
||||||
|
settings:
|
||||||
|
bucket: drone-test-results
|
||||||
|
access_key:
|
||||||
|
from_secret: test_results_aws_access_key_id
|
||||||
|
secret_key:
|
||||||
|
from_secret: test_results_aws_secret_access_key
|
||||||
|
endpoint: https://s3.fr-par.scw.cloud
|
||||||
|
region: fr-par
|
||||||
|
path_style: true
|
||||||
|
source: cypress/screenshots/**/**/*
|
||||||
|
strip_prefix: cypress/screenshots/
|
||||||
|
target: /${DRONE_REPO}/${DRONE_PULL_REQUEST}_${DRONE_BRANCH}/${DRONE_BUILD_NUMBER}/
|
||||||
|
depends_on:
|
||||||
|
- test-frontend
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- failure
|
||||||
|
- success
|
||||||
|
|
||||||
- name: deploy-preview
|
- name: deploy-preview
|
||||||
image: node:18-alpine
|
image: node:16
|
||||||
pull: true
|
pull: true
|
||||||
environment:
|
environment:
|
||||||
NETLIFY_AUTH_TOKEN:
|
NETLIFY_AUTH_TOKEN:
|
||||||
|
@ -145,10 +157,6 @@ steps:
|
||||||
GITEA_TOKEN:
|
GITEA_TOKEN:
|
||||||
from_secret: gitea_token
|
from_secret: gitea_token
|
||||||
commands:
|
commands:
|
||||||
- cp -r dist dist-preview
|
|
||||||
# Override the default api url used for preview
|
|
||||||
- sed -i 's|http://localhost:3456|https://try.vikunja.io|g' dist-preview/index.html
|
|
||||||
- apk add --no-cache perl-utils
|
|
||||||
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
|
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
|
||||||
- node ./scripts/deploy-preview-netlify.js
|
- node ./scripts/deploy-preview-netlify.js
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -191,22 +199,21 @@ steps:
|
||||||
# endpoint: https://s3.fr-par.scw.cloud
|
# endpoint: https://s3.fr-par.scw.cloud
|
||||||
# region: fr-par
|
# region: fr-par
|
||||||
# path_style: true
|
# path_style: true
|
||||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
|
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||||
# mount:
|
# mount:
|
||||||
# - .cache
|
# - '.cache'
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: node:18-alpine
|
image: node:16
|
||||||
pull: true
|
pull: true
|
||||||
group: build-static
|
group: build-static
|
||||||
environment:
|
environment:
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
YARN_CACHE_FOLDER: .cache/yarn/
|
||||||
commands:
|
commands:
|
||||||
- corepack enable && pnpm config set store-dir .cache/.pnp
|
- yarn --frozen-lockfile --network-timeout 100000
|
||||||
- pnpm install --fetch-timeout 100000
|
- yarn run lint
|
||||||
- pnpm run lint
|
|
||||||
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
|
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
|
||||||
- pnpm run build
|
- yarn run build
|
||||||
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
|
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
|
||||||
# depends_on:
|
# depends_on:
|
||||||
# - restore-cache
|
# - restore-cache
|
||||||
|
@ -267,22 +274,21 @@ steps:
|
||||||
# endpoint: https://s3.fr-par.scw.cloud
|
# endpoint: https://s3.fr-par.scw.cloud
|
||||||
# region: fr-par
|
# region: fr-par
|
||||||
# path_style: true
|
# path_style: true
|
||||||
# cache_key: '{{ .Repo.Name }}_{{ checksum "pnpm-lock.yaml" }}_{{ arch }}_{{ os }}'
|
# cache_key: '{{ .Repo.Name }}_{{ checksum "yarn.lock" }}_{{ arch }}_{{ os }}'
|
||||||
# mount:
|
# mount:
|
||||||
# - .cache
|
# - '.cache'
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: node:18-alpine
|
image: node:16
|
||||||
pull: true
|
pull: true
|
||||||
group: build-static
|
group: build-static
|
||||||
environment:
|
environment:
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
YARN_CACHE_FOLDER: .cache/yarn/
|
||||||
commands:
|
commands:
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
- yarn --frozen-lockfile --network-timeout 100000
|
||||||
- pnpm install --fetch-timeout 100000
|
- yarn run lint
|
||||||
- pnpm run lint
|
|
||||||
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
|
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
|
||||||
- pnpm run build
|
- yarn run build
|
||||||
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
|
- sed -i 's/http\:\\/\\/localhost\\:3456\\/api\\/v1/\\/api\\/v1/g' dist/index.html # Override the default api url used for developing
|
||||||
# depends_on:
|
# depends_on:
|
||||||
# - restore-cache
|
# - restore-cache
|
||||||
|
@ -659,6 +665,6 @@ steps:
|
||||||
from_secret: crowdin_key
|
from_secret: crowdin_key
|
||||||
---
|
---
|
||||||
kind: signature
|
kind: signature
|
||||||
hmac: c885a0e50db729842402494aa645dd3ac662828b691108550f6bf302158295ba
|
hmac: 188ee90100c5fc5922a445e531e7a47453121edddb2a64a182eb23ed2bf602de
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
1
.envrc
|
@ -1 +0,0 @@
|
||||||
use flake
|
|
|
@ -1,54 +0,0 @@
|
||||||
/* eslint-env node */
|
|
||||||
require("@rushstack/eslint-patch/modern-module-resolution")
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
'root': true,
|
|
||||||
'env': {
|
|
||||||
'browser': true,
|
|
||||||
'es2021': true,
|
|
||||||
'node': true,
|
|
||||||
'vue/setup-compiler-macros': true,
|
|
||||||
},
|
|
||||||
'extends': [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'@vue/eslint-config-typescript/recommended',
|
|
||||||
],
|
|
||||||
'rules': {
|
|
||||||
'vue/html-quotes': [
|
|
||||||
'error',
|
|
||||||
'double',
|
|
||||||
],
|
|
||||||
'quotes': [
|
|
||||||
'error',
|
|
||||||
'single',
|
|
||||||
],
|
|
||||||
'comma-dangle': [
|
|
||||||
'error',
|
|
||||||
'always-multiline',
|
|
||||||
],
|
|
||||||
'semi': [
|
|
||||||
'error',
|
|
||||||
'never',
|
|
||||||
],
|
|
||||||
|
|
||||||
// see https://segmentfault.com/q/1010000040813116/a-1020000041134455 (original in chinese)
|
|
||||||
'no-unused-vars': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
|
|
||||||
|
|
||||||
'vue/multi-word-component-names': 0,
|
|
||||||
},
|
|
||||||
'parser': 'vue-eslint-parser',
|
|
||||||
'parserOptions': {
|
|
||||||
'parser': '@typescript-eslint/parser',
|
|
||||||
'ecmaVersion': 2022,
|
|
||||||
'sourceType': 'module',
|
|
||||||
},
|
|
||||||
'ignorePatterns': [
|
|
||||||
'*.test.*',
|
|
||||||
'cypress/*',
|
|
||||||
],
|
|
||||||
'globals': {
|
|
||||||
'defineProps': 'readonly',
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
<!--
|
|
||||||
|
|
||||||
Please fill out this issue template to report a bug.
|
|
||||||
If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Version information:**
|
|
||||||
|
|
||||||
Frontend Version:
|
|
||||||
API Version:
|
|
||||||
Browser and OS Version:
|
|
||||||
|
|
||||||
**Steps to reproduce:**
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Add clear steps to reproduce the bug. Provide screenshots where applicable.
|
|
||||||
-->
|
|
||||||
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
...
|
|
||||||
|
|
||||||
**Expected behavior:**
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe what happened.
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Actual behavior:**
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe what happened instead.
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Checklist:**
|
|
||||||
|
|
||||||
* [ ] I have provided all required information
|
|
||||||
* [ ] I am using the latest release or the latest unstable build
|
|
||||||
* [ ] I was able to reproduce the bug on [try](https://try.vikunja.io)
|
|
58
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
|
@ -1,58 +0,0 @@
|
||||||
name: Bug Report
|
|
||||||
description: Found something you weren't expecting? Report it here!
|
|
||||||
labels: kind/bug
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Please fill out this issue template to report a bug.
|
|
||||||
|
|
||||||
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
|
|
||||||
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
|
|
||||||
3. Make sure you are using the latest release and
|
|
||||||
take a moment to check that your issue hasn't been reported before.
|
|
||||||
4. Please give all relevant information below for bug reports, because
|
|
||||||
incomplete details will be handled as an invalid report and closed.
|
|
||||||
- type: textarea
|
|
||||||
id: description
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: |
|
|
||||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
|
|
||||||
- type: input
|
|
||||||
id: frontend-version
|
|
||||||
attributes:
|
|
||||||
label: Vikunja Frontend Version
|
|
||||||
description: Vikunja frontend version (or commit reference) of your instance
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: api-version
|
|
||||||
attributes:
|
|
||||||
label: Vikunja API Version
|
|
||||||
description: Vikunja API version (or commit reference) of your instance
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: browser-version
|
|
||||||
attributes:
|
|
||||||
label: Browser and version
|
|
||||||
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
|
|
||||||
- type: dropdown
|
|
||||||
id: can-reproduce
|
|
||||||
attributes:
|
|
||||||
label: Can you reproduce the bug on the Vikunja demo site?
|
|
||||||
options:
|
|
||||||
- "Yes"
|
|
||||||
- "No"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: screenshots
|
|
||||||
attributes:
|
|
||||||
label: Screenshots
|
|
||||||
description: If this issue involves the Web Interface, please provide one or more screenshots
|
|
17
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,17 +0,0 @@
|
||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: API issues
|
|
||||||
url: https://code.vikunja.io/api/issues
|
|
||||||
about: This is the frontend repo. Please open api-related bug reports and discussions in the api 0repo. Not sure if your issue is frontend or api? Ask in Matrix or the forum first.
|
|
||||||
- name: Forum
|
|
||||||
url: https://community.vikunja.io/
|
|
||||||
about: Feature Requests, Questions, configuration or deployment problems should be discussed in the forum.
|
|
||||||
- name: Security-related issues
|
|
||||||
url: https://vikunja.io/contact/#security
|
|
||||||
about: For security concerns, please send a mail to security@vikunja.io instead of opening a public issue.
|
|
||||||
- name: Chat on Matrix
|
|
||||||
url: https://matrix.to/#/#vikunja:matrix.org
|
|
||||||
about: Please ask any quick questions here.
|
|
||||||
- name: Translations
|
|
||||||
url: https://crowdin.com/project/vikunja
|
|
||||||
about: Any problems or requests for new languages about translations should be handled in crowdin.
|
|
5
.gitignore
vendored
|
@ -2,21 +2,16 @@
|
||||||
node_modules
|
node_modules
|
||||||
/dist*
|
/dist*
|
||||||
*.zip
|
*.zip
|
||||||
.direnv/
|
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
||||||
# Log files
|
# Log files
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
stats.html
|
stats.html
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
|
|
2
.npmrc
|
@ -1,2 +0,0 @@
|
||||||
auto-install-peers=true
|
|
||||||
fetch-timeout=100000
|
|
2
.nvmrc
|
@ -1 +1 @@
|
||||||
v18
|
v16
|
|
@ -3,8 +3,7 @@
|
||||||
"codezombiech.gitignore",
|
"codezombiech.gitignore",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"editorconfig.editorconfig",
|
"editorconfig.editorconfig",
|
||||||
"vue.volar",
|
"johnsoncodehk.volar",
|
||||||
"vue.vscode-typescript-vue-plugin",
|
|
||||||
"lokalise.i18n-ally",
|
"lokalise.i18n-ally",
|
||||||
"mgmcdermott.vscode-language-babel",
|
"mgmcdermott.vscode-language-babel",
|
||||||
"mikestead.dotenv",
|
"mikestead.dotenv",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"eslint.packageManager": "pnpm",
|
"eslint.packageManager": "yarn",
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": true
|
||||||
|
@ -18,11 +18,13 @@
|
||||||
"javascriptreact",
|
"javascriptreact",
|
||||||
"vue"
|
"vue"
|
||||||
],
|
],
|
||||||
|
"vetur.validation.template": false,
|
||||||
|
|
||||||
// i18n ally
|
// i18n ally
|
||||||
"i18n-ally.localesPaths": [
|
"i18n-ally.localesPaths": [
|
||||||
"src/i18n/lang"
|
"src/i18n/lang"
|
||||||
],
|
],
|
||||||
"i18n-ally.sortKeys": true,
|
"i18n-ally.sortKeys": true,
|
||||||
"i18n-ally.keepFulfilled": true,
|
"i18n-ally.keepFulfilled": true,
|
||||||
"i18n-ally.keystyle": "nested"
|
"i18n-ally.keystyle": "nested",
|
||||||
}
|
}
|
2057
CHANGELOG.md
24
Dockerfile
|
@ -1,32 +1,28 @@
|
||||||
# Stage 1: Build application
|
# Stage 1: Build application
|
||||||
FROM node:18-alpine AS compile-image
|
FROM node:16 AS compile-image
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
ARG USE_RELEASE=false
|
ARG USE_RELEASE=false
|
||||||
ARG RELEASE_VERSION=main
|
ARG RELEASE_VERSION=main
|
||||||
|
|
||||||
ENV PNPM_CACHE_FOLDER .cache/pnpm/
|
ENV YARN_CACHE_FOLDER .cache/yarn/
|
||||||
ADD . ./
|
COPY . ./
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
if [ $USE_RELEASE = true ]; then \
|
if [ $USE_RELEASE = true ]; then \
|
||||||
|
rm -rf dist/ && \
|
||||||
wget https://dl.vikunja.io/frontend/vikunja-frontend-$RELEASE_VERSION.zip -O frontend-release.zip && \
|
wget https://dl.vikunja.io/frontend/vikunja-frontend-$RELEASE_VERSION.zip -O frontend-release.zip && \
|
||||||
unzip frontend-release.zip -d dist/ && \
|
unzip frontend-release.zip -d dist/ && \
|
||||||
exit 0; \
|
exit 0; \
|
||||||
fi && \
|
fi && \
|
||||||
# https://pnpm.io/installation#using-corepack
|
|
||||||
corepack enable && \
|
|
||||||
# we don't use corepack prepare here by intend since
|
|
||||||
# we have renovate to keep our dependencies up to date
|
|
||||||
# Build the frontend
|
# Build the frontend
|
||||||
pnpm install && \
|
yarn install --frozen-lockfile --network-timeout 100000 && \
|
||||||
apk add --no-cache git && \
|
|
||||||
echo '{"VERSION": "'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'"}' > src/version.json && \
|
echo '{"VERSION": "'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'"}' > src/version.json && \
|
||||||
pnpm run build
|
yarn run build
|
||||||
|
|
||||||
# Stage 2: copy
|
# Stage 2: copy
|
||||||
FROM nginx:alpine
|
FROM nginx
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY run.sh /run.sh
|
COPY run.sh /run.sh
|
||||||
|
@ -40,10 +36,4 @@ ENV PGID 1000
|
||||||
|
|
||||||
LABEL maintainer="maintainers@vikunja.io"
|
LABEL maintainer="maintainers@vikunja.io"
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
# for sh file
|
|
||||||
bash \
|
|
||||||
# installs usermod and groupmod
|
|
||||||
shadow
|
|
||||||
|
|
||||||
CMD "/run.sh"
|
CMD "/run.sh"
|
||||||
|
|
14
README.md
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
|
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
|
||||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||||
[![Download](https://img.shields.io/badge/download-v0.19.1-brightgreen.svg)](https://dl.vikunja.io)
|
[![Download](https://img.shields.io/badge/download-v0.18.2-brightgreen.svg)](https://dl.vikunja.io)
|
||||||
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
|
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
|
||||||
|
|
||||||
This is the web frontend for Vikunja, written in Vue.js.
|
This is the web frontend for Vikunja, written in Vue.js.
|
||||||
|
@ -22,27 +22,23 @@ There is a [docker image available](https://hub.docker.com/r/vikunja/api) with s
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
### Compiles and hot-reloads for development
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm run serve
|
yarn run serve
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and minifies for production
|
### Compiles and minifies for production
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm run build
|
yarn run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Lints and fixes files
|
### Lints and fixes files
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm run lint
|
yarn run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sponsors
|
|
||||||
|
|
||||||
[![Relm](https://vikunja.io/images/sponsors/relm.png)](https://relm.us)
|
|
||||||
|
|
91
android/.gitignore
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
# NPM renames .gitignore to .npmignore
|
||||||
|
# In order to prevent that, we remove the initial "."
|
||||||
|
# And the CLI then renames it
|
||||||
|
|
||||||
|
# Using Android gitignore template: https://github.com/github/gitignore/blob/master/Android.gitignore
|
||||||
|
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
#*.jks
|
||||||
|
#*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
||||||
|
# Cordova plugins for Capacitor
|
||||||
|
capacitor-cordova-android-plugins
|
||||||
|
|
||||||
|
# Copied web assets
|
||||||
|
app/src/main/assets/public
|
2
android/app/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/build/*
|
||||||
|
!/build/.npmkeep
|
46
android/app/build.gradle
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "io.vikunja.app"
|
||||||
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
flatDir{
|
||||||
|
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||||
|
implementation project(':capacitor-android')
|
||||||
|
testImplementation "junit:junit:$junitVersion"
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||||
|
implementation project(':capacitor-cordova-android-plugins')
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: 'capacitor.build.gradle'
|
||||||
|
|
||||||
|
try {
|
||||||
|
def servicesJSON = file('google-services.json')
|
||||||
|
if (servicesJSON.text) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
logger.warn("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||||
|
}
|
19
android/app/capacitor.build.gradle
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hasProperty('postBuildExtras')) {
|
||||||
|
postBuildExtras()
|
||||||
|
}
|
21
android/app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
63
android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="io.vikunja.app">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||||
|
android:name="io.vikunja.app.MainActivity"
|
||||||
|
android:label="@string/title_activity_main"
|
||||||
|
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="@string/custom_url_scheme" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths"></meta-data>
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<!-- Permissions -->
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<!-- Camera, Photos, input file -->
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<!-- Geolocation API -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-feature android:name="android.hardware.location.gps" />
|
||||||
|
<!-- Network API -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<!-- Navigator.getUserMedia -->
|
||||||
|
<!-- Video -->
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<!-- Audio -->
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||||
|
</manifest>
|
6
android/app/src/main/assets/capacitor.config.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"appId": "io.vikunja.app",
|
||||||
|
"appName": "Vikunja",
|
||||||
|
"webDir": "dist",
|
||||||
|
"bundledWebRuntime": false
|
||||||
|
}
|
1
android/app/src/main/assets/capacitor.plugins.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[]
|
BIN
android/app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 26 KiB |
21
android/app/src/main/java/io/vikunja/app/MainActivity.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package io.vikunja.app;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.getcapacitor.BridgeActivity;
|
||||||
|
import com.getcapacitor.Plugin;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class MainActivity extends BridgeActivity {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Initializes the Bridge
|
||||||
|
this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
|
||||||
|
// Additional plugins you've installed go here
|
||||||
|
// Ex: add(TotallyAwesomePlugin.class);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
After Width: | Height: | Size: 26 KiB |
101
android/app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.29109365"
|
||||||
|
android:scaleY="0.29109365"
|
||||||
|
android:translateX="16.740015"
|
||||||
|
android:translateY="16.74">
|
||||||
|
<path
|
||||||
|
android:pathData="m167.62,161.652c4.758,-13.899 12.617,-18.154 13.594,-39.601 0.343,-7.537 -1.955,-31.477 -1.955,-31.477 0,-0 3.735,-9.599 -3.443,-20.292 -11.13,-16.58 -34.053,-17.707 -46.092,-17.707 -12.039,-0 -34.694,1.127 -45.825,17.707 -7.179,10.693 -3.444,20.292 -3.444,20.292 0,-0 -1.271,12.313 -2.234,23.172 -0.227,2.548 0.021,12.629 0.327,15.064 0.55,4.364 1.931,8.652 3.441,12.826 1.912,5.285 4.909,10.648 7.198,16.07 6.907,16.371 1.127,36.647 1.48,54.3 0.298,12.531 -2.668,25.242 -3.831,37.496 13.084,4.434 26.945,6.747 41.162,6.747 15.978,-0 31.507,-2.913 45.987,-8.484 -2.719,-15.342 -5.879,-29.894 -6.366,-35.446 -1.419,-16.785 -5.507,-34.587 -0.001,-50.667"
|
||||||
|
android:strokeWidth="0.048874177"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m79.507,63.921c0.153,2.497 0.4,5.057 1.103,7.488 0.221,0.758 0.547,2.624 1.522,2.902 1.212,0.345 3.455,-1.101 4.305,-1.728 1.648,-1.213 3.19,-2.461 4.838,-3.687 1.051,-0.782 2.199,-1.396 3.228,-2.199 1.12,-0.873 1.963,-1.93 2.895,-2.957 1.131,-1.248 1.98,-2.54 2.85,-3.933 0.68,-1.087 4.943,-1.795 5.625,-2.804 0.745,-1.103 0.122,-2.468 0.122,-3.826 0,-9.889 0.594,-19.675 0.825,-29.372 0.238,-6.652 2.851,-14.55 -7.125,-11.432 -21.375,6.652 -21.137,36.168 -20.187,51.549"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m173.67,77.809c-0.215,-0.233 -0.559,-0.432 -0.835,-0.636 -1.197,-0.887 -2.326,-1.814 -3.354,-2.864 -1.255,-1.282 -2.559,-2.528 -3.755,-3.857 -1.052,-1.168 -2.308,-2.214 -3.334,-3.41 -0.684,-0.795 -1.344,-1.641 -1.948,-2.475 -0.308,-0.423 -0.469,-0.933 -0.712,-1.386 -0.361,-0.668 -5.318,-1.349 -4.825,-2.001 0.84,-1.111 2.71,-2.053 3.16,-3.325 2.299,-6.489 3.303,-32.603 3.58,-39.268 0.714,-17.044 15.676,0.832 18.289,7.691 7.601,19.954 3.086,34.504 -6.175,51.549"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m153.319,143.388c-0.011,-9.841 -1.234,-13.822 -6.721,-18.634 -3.432,-3.009 -7.445,-4.157 -13.832,-3.999 -6.387,-0.158 -10.99,1.784 -14.309,5.23 -5.007,5.199 -6.325,8.274 -6.339,18.114 -0.008,5.319 5.727,17.089 7.434,19.805 2.832,4.791 8.075,6.513 13.214,6.154 5.139,0.359 10.382,-1.363 13.213,-6.154 1.708,-2.716 7.348,-14.261 7.34,-20.517"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#f1e6d3"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m88.003,72.959c-1.282,-11.478 -5.144,-31.997 8.198,-44.165 1.472,-1.343 1.867,3.227 2.113,5.075 0.338,2.538 -0.007,9.789 0.045,12.344 0.095,4.812 1.103,7.329 0.634,9.528 -1.284,6.012 -2.314,5.624 -3.541,9.28 -2.434,7.25 -7.098,11.075 -7.449,7.937"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#f1d7d4"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m171.355,70.691c1.366,2.444 4.933,-10.701 6.051,-16.41 1.053,-5.374 2.39,-12.516 0.541,-17.855 -0.992,-2.864 -7.077,-11.276 -7.795,-8.386 -0.28,1.122 -0.424,9.48 -1.113,13.479 -0.852,4.948 -2.388,9.841 -2.397,14.873 -0.02,11.276 3.378,11.741 4.713,14.299"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#f1d7d4"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m127.391,205.803c-0.289,0.878 -2.479,1.521 -3.302,1.786 -1.988,0.642 -3.753,-0.925 -4.863,-2.246 -1.544,-1.831 -1.503,-4.751 -2.15,-6.886 -0.391,-1.291 -2.604,-1.466 -3.269,-0.272 -2.217,3.987 -10.821,-3.515 -8.822,-6.013 1.318,-1.647 -1.838,-3.102 -3.139,-1.477 -3.86,4.821 6.523,13.799 12.477,11.366 0.265,1.205 0.543,2.408 1.055,3.551 1.003,2.247 3.197,3.698 5.468,4.919 3.093,1.658 9.101,-1.254 10.019,-4.034 0.639,-1.934 -2.837,-2.618 -3.474,-0.695"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#faeee0"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m154.806,176.672c0.319,1.339 0.37,5.876 -2.125,3.394 -1.012,-1.007 -3.022,-0.413 -3.104,0.934 -0.193,3.118 -3.32,3.187 -6.018,2.244 -2.141,-0.747 -3.271,2.212 -1.147,2.953 3.962,1.382 8.261,0.616 9.97,-2.378 1.344,0.633 2.865,0.805 4.084,-0.342 2.129,-2.006 2.45,-4.83 1.831,-7.428 -0.47,-1.968 -3.96,-1.346 -3.49,0.623"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#faeee0"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m161.128,241.87c-0.934,1.909 -2.647,1.79 -4.485,0.988 -1.348,-0.587 -3.014,0.383 -2.608,1.748 0.818,2.736 -2.638,3.535 -4.975,2.559 -2.057,-0.859 -3.845,1.839 -1.766,2.708 4.211,1.757 9.564,0.22 10.34,-3.485 2.708,0.429 5.295,-0.331 6.586,-2.975 0.894,-1.824 -2.187,-3.396 -3.093,-1.544"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#faeee0"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m133.758,94.668c-0.434,2.691 -5.479,-0.443 -6.732,-1.292 -1.103,-0.746 -2.53,-0.293 -2.664,0.965 -0.18,1.695 -2.464,1.203 -3.508,0.99 -1.974,-0.401 -3.649,-1.632 -3.696,-3.481 -0.046,-1.796 1.094,-3.683 1.689,-5.369 0.38,-1.077 -1.163,-2.262 -2.291,-1.542 -2.032,1.297 -11.829,2.464 -7.927,-1.806 1.243,-1.36 -0.951,-3.306 -2.207,-1.931 -1.802,1.973 -3.021,6.493 0.878,7.45 2.058,0.505 5.037,0.479 7.575,-0.1 -1.06,3.397 -1.282,6.507 3.087,8.689 2.953,1.476 7.008,1.465 8.726,-0.814 3.615,1.944 9.295,3.831 10.08,-1.035 0.278,-1.721 -2.728,-2.464 -3.009,-0.725"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#faeee0"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m116.859,157.027c5.249,0.444 10.5,1.636 15.797,1.445 3.386,-0.123 6.892,-0.597 10.233,-1.093 2.427,-0.359 5.403,-0.784 7.026,-2.583 0.152,6.002 -12.335,7.881 -17.178,8.003 -5.619,0.142 -11.935,-2.296 -15.878,-5.772"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#494949"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m131.049,144.504c-0.6,3.853 -0.724,7.76 -0.363,11.636 0.096,1.013 -0.174,3.428 1.173,3.936 1.092,0.413 2.442,-0.363 2.62,-1.348 0.301,-1.661 -0.405,-3.755 -0.465,-5.456 -0.071,-1.992 0,-3.987 0.211,-5.969 0.126,-1.175 0.785,-2.761 -0.56,-3.589 -0.987,-0.607 -2.434,-0.383 -2.616,0.79"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#494949"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m141.253,135.138c-3.155,0.136 -6.084,1.902 -8.453,3.603 -2.144,-1.423 -5.391,-2.731 -8.092,-1.97 -1.61,0.452 -0.148,0.946 0.524,1.375 1.241,0.825 2.253,1.886 3.166,2.982 1.385,1.68 2.617,3.444 4.095,5.064 1.023,-0.737 2,-1.48 2.89,-2.341 0.413,-0.399 3.791,-4.923 4.31,-5.689 0.331,-0.487 0.665,-0.991 1.045,-1.45 0.385,-0.464 1.476,-1.202 0.517,-1.575"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#494949"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m101.325,113.765c1.558,-4.04 3.232,-9.118 7.502,-8.31 3.233,0.693 7.56,11.482 4.79,10.417 -1.5,-0.577 -2.694,-5.481 -5.511,-6.117 -1.79,-0.404 -4.271,5.8 -6.666,6.435 -1.161,0.307 -0.577,-1.385 -0.115,-2.424"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#2c3844"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m147.202,113.765c1.558,-4.04 3.233,-9.118 7.502,-8.31 3.232,0.693 7.559,11.482 4.79,10.417 -1.5,-0.577 -2.694,-5.481 -5.511,-6.117 -1.79,-0.404 -4.271,5.8 -6.665,6.435 -1.161,0.307 -0.577,-1.385 -0.116,-2.424"
|
||||||
|
android:strokeWidth="0.04884417"
|
||||||
|
android:fillColor="#2c3844"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
BIN
android/app/src/main/res/drawable/splash.png
Normal file
After Width: | Height: | Size: 6 KiB |
12
android/app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#1973FF</color>
|
||||||
|
</resources>
|
7
android/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Vikunja</string>
|
||||||
|
<string name="title_activity_main">Vikunja</string>
|
||||||
|
<string name="package_name">io.vikunja.app</string>
|
||||||
|
<string name="custom_url_scheme">io.vikunja.app</string>
|
||||||
|
</resources>
|
22
android/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.NoActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:background">@null</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBarLaunch" parent="AppTheme.NoActionBar">
|
||||||
|
<item name="android:background">@drawable/splash</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
6
android/app/src/main/res/xml/config.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
|
<access origin="*" />
|
||||||
|
|
||||||
|
|
||||||
|
</widget>
|
5
android/app/src/main/res/xml/file_paths.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-path name="my_images" path="." />
|
||||||
|
<cache-path name="my_cache_images" path="." />
|
||||||
|
</paths>
|
29
android/build.gradle
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.6.1'
|
||||||
|
classpath 'com.google.gms:google-services:4.3.3'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "variables.gradle"
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
3
android/capacitor.settings.gradle
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
include ':capacitor-android'
|
||||||
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
24
android/gradle.properties
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=true
|
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
188
android/gradlew
vendored
Executable file
|
@ -0,0 +1,188 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
100
android/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
5
android/settings.gradle
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
include ':app'
|
||||||
|
include ':capacitor-cordova-android-plugins'
|
||||||
|
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||||
|
|
||||||
|
apply from: 'capacitor.settings.gradle'
|
17
android/variables.gradle
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
ext {
|
||||||
|
minSdkVersion = 21
|
||||||
|
compileSdkVersion = 29
|
||||||
|
targetSdkVersion = 29
|
||||||
|
androidxAppCompatVersion = '1.1.0'
|
||||||
|
androidxCoreVersion = '1.2.0'
|
||||||
|
androidxMaterialVersion = '1.1.0-rc02'
|
||||||
|
androidxBrowserVersion = '1.2.0'
|
||||||
|
androidxLocalbroadcastmanagerVersion = '1.0.0'
|
||||||
|
androidxExifInterfaceVersion = '1.2.0'
|
||||||
|
firebaseMessagingVersion = '20.1.2'
|
||||||
|
playServicesLocationVersion = '17.0.0'
|
||||||
|
junitVersion = '4.12'
|
||||||
|
androidxJunitVersion = '1.1.1'
|
||||||
|
androidxEspressoCoreVersion = '3.2.0'
|
||||||
|
cordovaAndroidVersion = '7.0.0'
|
||||||
|
}
|
10
capacitor.config.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { CapacitorConfig } from '@capacitor/cli'
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: 'io.vikunja.app',
|
||||||
|
appName: 'Vikunja',
|
||||||
|
webDir: 'dist',
|
||||||
|
bundledWebRuntime: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
59
cliff.toml
|
@ -1,59 +0,0 @@
|
||||||
[changelog]
|
|
||||||
body = """
|
|
||||||
{% if version %}\
|
|
||||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
||||||
{% else %}\
|
|
||||||
## [unreleased]
|
|
||||||
{% endif %}\
|
|
||||||
|
|
||||||
|
|
||||||
{% for group, commits in commits | group_by(attribute="group") %}
|
|
||||||
### {{ group | upper_first }}
|
|
||||||
{% for commit in commits
|
|
||||||
| filter(attribute="scope")
|
|
||||||
| sort(attribute="scope") %}
|
|
||||||
* *({{commit.scope}})* {{ commit.message | upper_first }}
|
|
||||||
{%- if commit.breaking %}
|
|
||||||
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
|
|
||||||
{%- endif -%}
|
|
||||||
{%- endfor -%}
|
|
||||||
{%- for commit in commits %}
|
|
||||||
{%- if commit.scope -%}
|
|
||||||
{% else -%}
|
|
||||||
* {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
|
|
||||||
{% if commit.breaking -%}
|
|
||||||
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
|
|
||||||
{% endif -%}
|
|
||||||
{% endif -%}
|
|
||||||
{% endfor -%}
|
|
||||||
{% raw %}\n{% endraw %}\
|
|
||||||
{% endfor %}\n
|
|
||||||
|
|
||||||
"""
|
|
||||||
#{% for group, commits in commits | group_by(attribute="group") %}
|
|
||||||
# ### {{ group | upper_first }}
|
|
||||||
# {% for commit in commits %}\
|
|
||||||
# - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
|
|
||||||
# {% endfor %}\
|
|
||||||
#{% endfor %}\n
|
|
||||||
# remove the leading and trailing whitespace from the template
|
|
||||||
trim = true
|
|
||||||
|
|
||||||
[git]
|
|
||||||
conventional_commits = true
|
|
||||||
filter_unconventional = false
|
|
||||||
commit_parsers = [
|
|
||||||
{ message = ".*(deps).*", group = "Dependencies"},
|
|
||||||
{ message = "^feat", group = "Features"},
|
|
||||||
{ message = "^fix", group = "Bug Fixes"},
|
|
||||||
{ message = "^doc", group = "Documentation"},
|
|
||||||
{ message = "^perf", group = "Performance"},
|
|
||||||
{ message = "^refactor", group = "Refactor"},
|
|
||||||
{ message = "^style", group = "Styling"},
|
|
||||||
{ message = "^test", group = "Testing"},
|
|
||||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
|
||||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
|
||||||
{ body = ".*security", group = "Security"},
|
|
||||||
{ message = ".*", group = "Other", default_scope = "other"}, # Everything that's not a conventional commit goes into the "Other" category
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import {defineConfig} from 'cypress'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
env: {
|
|
||||||
API_URL: 'http://localhost:3456/api/v1',
|
|
||||||
TEST_SECRET: 'averyLongSecretToSe33dtheDB',
|
|
||||||
},
|
|
||||||
video: false,
|
|
||||||
retries: {
|
|
||||||
runMode: 2,
|
|
||||||
},
|
|
||||||
projectId: '181c7x',
|
|
||||||
e2e: {
|
|
||||||
baseUrl: 'http://localhost:4173',
|
|
||||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
|
||||||
},
|
|
||||||
component: {
|
|
||||||
devServer: {
|
|
||||||
framework: 'vue',
|
|
||||||
bundler: 'vite',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
viewportWidth: 1600,
|
|
||||||
viewportHeight: 900,
|
|
||||||
})
|
|
11
cypress.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"baseUrl": "http://localhost:5000",
|
||||||
|
"env": {
|
||||||
|
"API_URL": "http://localhost:3456/api/v1",
|
||||||
|
"TEST_SECRET": "averyLongSecretToSe33dtheDB"
|
||||||
|
},
|
||||||
|
"video": false,
|
||||||
|
"retries": {
|
||||||
|
"runMode": 2
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ to get a shell inside the cypress container.
|
||||||
In that shell you can then execute the tests with
|
In that shell you can then execute the tests with
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm run test:frontend
|
yarn test:frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using The Cypress Dashboard
|
### Using The Cypress Dashboard
|
||||||
|
@ -44,5 +44,5 @@ pnpm run test:frontend
|
||||||
To open the Cypress Dashboard and run tests from there, run
|
To open the Cypress Dashboard and run tests from there, run
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm run cypress:open
|
yarn cypress:open
|
||||||
```
|
```
|
||||||
|
|
|
@ -9,7 +9,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- 3456:3456
|
- 3456:3456
|
||||||
cypress:
|
cypress:
|
||||||
image: cypress/browsers:node16.14.0-chrome99-ff97
|
image: cypress/browsers:node12.18.3-chrome87-ff82
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/project
|
- ..:/project
|
||||||
- $HOME/.cache:/home/node/.cache/
|
- $HOME/.cache:/home/node/.cache/
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
import {ListFactory} from '../../factories/list'
|
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
import {prepareLists} from './prepareLists'
|
|
||||||
|
|
||||||
describe('List History', () => {
|
|
||||||
prepareLists()
|
|
||||||
|
|
||||||
it('should show a list history on the home page', () => {
|
|
||||||
cy.intercept(Cypress.env('API_URL') + '/namespaces*').as('loadNamespaces')
|
|
||||||
cy.intercept(Cypress.env('API_URL') + '/lists/*').as('loadList')
|
|
||||||
|
|
||||||
const lists = ListFactory.create(6)
|
|
||||||
|
|
||||||
cy.visit('/')
|
|
||||||
cy.wait('@loadNamespaces')
|
|
||||||
cy.get('body')
|
|
||||||
.should('not.contain', 'Last viewed')
|
|
||||||
|
|
||||||
cy.visit(`/lists/${lists[0].id}`)
|
|
||||||
cy.wait('@loadNamespaces')
|
|
||||||
cy.wait('@loadList')
|
|
||||||
cy.visit(`/lists/${lists[1].id}`)
|
|
||||||
cy.wait('@loadNamespaces')
|
|
||||||
cy.wait('@loadList')
|
|
||||||
cy.visit(`/lists/${lists[2].id}`)
|
|
||||||
cy.wait('@loadNamespaces')
|
|
||||||
cy.wait('@loadList')
|
|
||||||
cy.visit(`/lists/${lists[3].id}`)
|
|
||||||
cy.wait('@loadNamespaces')
|
|
||||||
cy.wait('@loadList')
|
|
||||||
cy.visit(`/lists/${lists[4].id}`)
|
|
||||||
cy.wait('@loadNamespaces')
|
|
||||||
cy.wait('@loadList')
|
|
||||||
cy.visit(`/lists/${lists[5].id}`)
|
|
||||||
cy.wait('@loadNamespaces')
|
|
||||||
cy.wait('@loadList')
|
|
||||||
|
|
||||||
// cy.visit('/')
|
|
||||||
// cy.wait('@loadNamespaces')
|
|
||||||
// Not using cy.visit here to work around the redirect issue fixed in #1337
|
|
||||||
cy.get('nav.menu.top-menu a')
|
|
||||||
.contains('Overview')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.should('contain', 'Last viewed')
|
|
||||||
cy.get('.list-cards-wrapper-2-rows')
|
|
||||||
.should('not.contain', lists[0].title)
|
|
||||||
.should('contain', lists[1].title)
|
|
||||||
.should('contain', lists[2].title)
|
|
||||||
.should('contain', lists[3].title)
|
|
||||||
.should('contain', lists[4].title)
|
|
||||||
.should('contain', lists[5].title)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,78 +0,0 @@
|
||||||
import {formatISO, format} from 'date-fns'
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
|
||||||
import {prepareLists} from './prepareLists'
|
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View Gantt', () => {
|
|
||||||
prepareLists()
|
|
||||||
|
|
||||||
it('Hides tasks with no dates', () => {
|
|
||||||
const tasks = TaskFactory.create(1)
|
|
||||||
cy.visit('/lists/1/gantt')
|
|
||||||
|
|
||||||
cy.get('.gantt-chart .tasks')
|
|
||||||
.should('not.contain', tasks[0].title)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Shows tasks from the current and next month', () => {
|
|
||||||
const now = Date.UTC(2022, 8, 25)
|
|
||||||
cy.clock(now, ['Date'])
|
|
||||||
|
|
||||||
const nextMonth = new Date(now)
|
|
||||||
nextMonth.setDate(1)
|
|
||||||
nextMonth.setMonth(9)
|
|
||||||
|
|
||||||
cy.visit('/lists/1/gantt')
|
|
||||||
|
|
||||||
cy.get('.gantt-chart .months')
|
|
||||||
.should('contain', format(now, 'MMMM'))
|
|
||||||
.should('contain', format(nextMonth, 'MMMM'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Shows tasks with dates', () => {
|
|
||||||
const now = new Date()
|
|
||||||
const tasks = TaskFactory.create(1, {
|
|
||||||
start_date: formatISO(now),
|
|
||||||
end_date: formatISO(now.setDate(now.getDate() + 4)),
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/gantt')
|
|
||||||
|
|
||||||
cy.get('.gantt-chart .tasks')
|
|
||||||
.should('not.be.empty')
|
|
||||||
cy.get('.gantt-chart .tasks')
|
|
||||||
.should('contain', tasks[0].title)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Shows tasks with no dates after enabling them', () => {
|
|
||||||
TaskFactory.create(1, {
|
|
||||||
start_date: null,
|
|
||||||
end_date: null,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/gantt')
|
|
||||||
|
|
||||||
cy.get('.gantt-options .fancycheckbox')
|
|
||||||
.contains('Show tasks which don\'t have dates set')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.gantt-chart .tasks')
|
|
||||||
.should('not.be.empty')
|
|
||||||
cy.get('.gantt-chart .tasks .task.nodate')
|
|
||||||
.should('exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Drags a task around', () => {
|
|
||||||
const now = new Date()
|
|
||||||
TaskFactory.create(1, {
|
|
||||||
start_date: formatISO(now),
|
|
||||||
end_date: formatISO(now.setDate(now.getDate() + 4)),
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/gantt')
|
|
||||||
|
|
||||||
cy.get('.gantt-chart .tasks .task')
|
|
||||||
.first()
|
|
||||||
.trigger('mousedown', {which: 1})
|
|
||||||
.trigger('mousemove', {clientX: 500, clientY: 0})
|
|
||||||
.trigger('mouseup', {force: true})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,196 +0,0 @@
|
||||||
import {BucketFactory} from '../../factories/bucket'
|
|
||||||
import {ListFactory} from '../../factories/list'
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
|
||||||
import {prepareLists} from './prepareLists'
|
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View Kanban', () => {
|
|
||||||
let buckets
|
|
||||||
prepareLists()
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
buckets = BucketFactory.create(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Shows all buckets with their tasks', () => {
|
|
||||||
const data = TaskFactory.create(10, {
|
|
||||||
list_id: 1,
|
|
||||||
bucket_id: 1,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.get('.kanban .bucket .title')
|
|
||||||
.contains(buckets[0].title)
|
|
||||||
.should('exist')
|
|
||||||
cy.get('.kanban .bucket .title')
|
|
||||||
.contains(buckets[1].title)
|
|
||||||
.should('exist')
|
|
||||||
cy.get('.kanban .bucket')
|
|
||||||
.first()
|
|
||||||
.should('contain', data[0].title)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can add a new task to a bucket', () => {
|
|
||||||
TaskFactory.create(2, {
|
|
||||||
list_id: 1,
|
|
||||||
bucket_id: 1,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket')
|
|
||||||
.contains(buckets[0].title)
|
|
||||||
.get('.bucket-footer .button')
|
|
||||||
.contains('Add another task')
|
|
||||||
.click()
|
|
||||||
cy.get('.kanban .bucket')
|
|
||||||
.contains(buckets[0].title)
|
|
||||||
.get('.bucket-footer .field .control input.input')
|
|
||||||
.type('New Task{enter}')
|
|
||||||
|
|
||||||
cy.get('.kanban .bucket')
|
|
||||||
.first()
|
|
||||||
.should('contain', 'New Task')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can create a new bucket', () => {
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.get('.kanban .bucket.new-bucket .button')
|
|
||||||
.click()
|
|
||||||
cy.get('.kanban .bucket.new-bucket input.input')
|
|
||||||
.type('New Bucket{enter}')
|
|
||||||
|
|
||||||
cy.wait(1000) // Wait for the request to finish
|
|
||||||
cy.get('.kanban .bucket .title')
|
|
||||||
.contains('New Bucket')
|
|
||||||
.should('exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can set a bucket limit', () => {
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
|
||||||
.contains('Limit: Not Set')
|
|
||||||
.click()
|
|
||||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
|
|
||||||
.first()
|
|
||||||
.type(3)
|
|
||||||
cy.get('[data-cy="setBucketLimit"]')
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.kanban .bucket .bucket-header span.limit')
|
|
||||||
.contains('0/3')
|
|
||||||
.should('exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can rename a bucket', () => {
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .bucket-header .title')
|
|
||||||
.first()
|
|
||||||
.type('{selectall}New Bucket Title{enter}')
|
|
||||||
cy.get('.kanban .bucket .bucket-header .title')
|
|
||||||
.first()
|
|
||||||
.should('contain', 'New Bucket Title')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can delete a bucket', () => {
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
|
||||||
.contains('Delete')
|
|
||||||
.click()
|
|
||||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
|
||||||
.should('contain', 'Delete the bucket')
|
|
||||||
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
|
||||||
.contains('Do it!')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.kanban .bucket .title')
|
|
||||||
.contains(buckets[0].title)
|
|
||||||
.should('not.exist')
|
|
||||||
cy.get('.kanban .bucket .title')
|
|
||||||
.contains(buckets[1].title)
|
|
||||||
.should('exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can drag tasks around', () => {
|
|
||||||
const tasks = TaskFactory.create(2, {
|
|
||||||
list_id: 1,
|
|
||||||
bucket_id: 1,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks .task')
|
|
||||||
.contains(tasks[0].title)
|
|
||||||
.first()
|
|
||||||
.drag('.kanban .bucket:nth-child(2) .tasks')
|
|
||||||
|
|
||||||
cy.get('.kanban .bucket:nth-child(2) .tasks')
|
|
||||||
.should('contain', tasks[0].title)
|
|
||||||
cy.get('.kanban .bucket:nth-child(1) .tasks')
|
|
||||||
.should('not.contain', tasks[0].title)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should navigate to the task when the task card is clicked', () => {
|
|
||||||
const tasks = TaskFactory.create(5, {
|
|
||||||
id: '{increment}',
|
|
||||||
list_id: 1,
|
|
||||||
bucket_id: 1,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks .task')
|
|
||||||
.contains(tasks[0].title)
|
|
||||||
.should('be.visible')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.url()
|
|
||||||
.should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should remove a task from the kanban board when moving it to another list', () => {
|
|
||||||
const lists = ListFactory.create(2)
|
|
||||||
BucketFactory.create(2, {
|
|
||||||
list_id: '{increment}',
|
|
||||||
})
|
|
||||||
const tasks = TaskFactory.create(5, {
|
|
||||||
id: '{increment}',
|
|
||||||
list_id: 1,
|
|
||||||
bucket_id: 1,
|
|
||||||
})
|
|
||||||
const task = tasks[0]
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks .task')
|
|
||||||
.contains(task.title)
|
|
||||||
.should('be.visible')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button', { timeout: 3000 })
|
|
||||||
.contains('Move')
|
|
||||||
.click()
|
|
||||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
|
||||||
.type(`${lists[1].title}{enter}`)
|
|
||||||
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
|
|
||||||
// presses enter and we can't simulate pressing on enter to select the item.
|
|
||||||
cy.get('.task-view .content.details .field .multiselect.control .search-results')
|
|
||||||
.children()
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.global-notification', { timeout: 1000 })
|
|
||||||
.should('contain', 'Success')
|
|
||||||
cy.go('back')
|
|
||||||
cy.get('.kanban .bucket')
|
|
||||||
.should('not.contain', task.title)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,109 +0,0 @@
|
||||||
import {UserListFactory} from '../../factories/users_list'
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
|
||||||
import {UserFactory} from '../../factories/user'
|
|
||||||
import {ListFactory} from '../../factories/list'
|
|
||||||
import {prepareLists} from './prepareLists'
|
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View List', () => {
|
|
||||||
prepareLists()
|
|
||||||
|
|
||||||
it('Should be an empty list', () => {
|
|
||||||
cy.visit('/lists/1')
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/lists/1/list')
|
|
||||||
cy.get('.list-title h1')
|
|
||||||
.should('contain', 'First List')
|
|
||||||
cy.get('.list-title .dropdown')
|
|
||||||
.should('exist')
|
|
||||||
cy.get('p')
|
|
||||||
.contains('This list is currently empty.')
|
|
||||||
.should('exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should create a new task', () => {
|
|
||||||
const newTaskTitle = 'New task'
|
|
||||||
|
|
||||||
cy.visit('/lists/1')
|
|
||||||
cy.get('.task-add textarea')
|
|
||||||
.type(newTaskTitle+'{enter}')
|
|
||||||
cy.get('.tasks')
|
|
||||||
.should('contain.text', newTaskTitle)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should navigate to the task when the title is clicked', () => {
|
|
||||||
const tasks = TaskFactory.create(5, {
|
|
||||||
id: '{increment}',
|
|
||||||
list_id: 1,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/list')
|
|
||||||
|
|
||||||
cy.get('.tasks .task .tasktext')
|
|
||||||
.contains(tasks[0].title)
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.url()
|
|
||||||
.should('contain', `/tasks/${tasks[0].id}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not see any elements for a list which is shared read only', () => {
|
|
||||||
UserFactory.create(2)
|
|
||||||
UserListFactory.create(1, {
|
|
||||||
list_id: 2,
|
|
||||||
user_id: 1,
|
|
||||||
right: 0,
|
|
||||||
})
|
|
||||||
const lists = ListFactory.create(2, {
|
|
||||||
owner_id: '{increment}',
|
|
||||||
namespace_id: '{increment}',
|
|
||||||
})
|
|
||||||
cy.visit(`/lists/${lists[1].id}/`)
|
|
||||||
|
|
||||||
cy.get('.list-title .icon')
|
|
||||||
.should('not.exist')
|
|
||||||
cy.get('input.input[placeholder="Add a new task..."')
|
|
||||||
.should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should only show the color of a list in the navigation and not in the list view', () => {
|
|
||||||
const lists = ListFactory.create(1, {
|
|
||||||
hex_color: '00db60',
|
|
||||||
})
|
|
||||||
TaskFactory.create(10, {
|
|
||||||
list_id: lists[0].id,
|
|
||||||
})
|
|
||||||
cy.visit(`/lists/${lists[0].id}/`)
|
|
||||||
|
|
||||||
cy.get('.menu-list li .list-menu-link .color-bubble')
|
|
||||||
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
|
|
||||||
cy.get('.tasks-container .tasks .color-bubble')
|
|
||||||
.should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should paginate for > 50 tasks', () => {
|
|
||||||
const tasks = TaskFactory.create(100, {
|
|
||||||
id: '{increment}',
|
|
||||||
title: i => `task${i}`,
|
|
||||||
list_id: 1,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/list')
|
|
||||||
|
|
||||||
cy.get('.tasks-container .tasks')
|
|
||||||
.should('contain', tasks[1].title)
|
|
||||||
cy.get('.tasks-container .tasks')
|
|
||||||
.should('not.contain', tasks[99].title)
|
|
||||||
|
|
||||||
cy.get('.card-content .pagination .pagination-link')
|
|
||||||
.contains('2')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '?page=2')
|
|
||||||
cy.get('.tasks-container .tasks')
|
|
||||||
.should('contain', tasks[99].title)
|
|
||||||
cy.get('.tasks-container .tasks')
|
|
||||||
.should('not.contain', tasks[1].title)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,52 +0,0 @@
|
||||||
import {TaskFactory} from '../../factories/task'
|
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View Table', () => {
|
|
||||||
it('Should show a table with tasks', () => {
|
|
||||||
const tasks = TaskFactory.create(1)
|
|
||||||
cy.visit('/lists/1/table')
|
|
||||||
|
|
||||||
cy.get('.list-table table.table')
|
|
||||||
.should('exist')
|
|
||||||
cy.get('.list-table table.table')
|
|
||||||
.should('contain', tasks[0].title)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should have working column switches', () => {
|
|
||||||
TaskFactory.create(1)
|
|
||||||
cy.visit('/lists/1/table')
|
|
||||||
|
|
||||||
cy.get('.list-table .filter-container .items .button')
|
|
||||||
.contains('Columns')
|
|
||||||
.click()
|
|
||||||
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
|
||||||
.contains('Priority')
|
|
||||||
.click()
|
|
||||||
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
|
||||||
.contains('Done')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.list-table table.table th')
|
|
||||||
.contains('Priority')
|
|
||||||
.should('exist')
|
|
||||||
cy.get('.list-table table.table th')
|
|
||||||
.contains('Done')
|
|
||||||
.should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should navigate to the task when the title is clicked', () => {
|
|
||||||
const tasks = TaskFactory.create(5, {
|
|
||||||
id: '{increment}',
|
|
||||||
list_id: 1,
|
|
||||||
})
|
|
||||||
cy.visit('/lists/1/table')
|
|
||||||
|
|
||||||
cy.get('.list-table table.table')
|
|
||||||
.contains(tasks[0].title)
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.url()
|
|
||||||
.should('contain', `/tasks/${tasks[0].id}`)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,120 +0,0 @@
|
||||||
import {TaskFactory} from '../../factories/task'
|
|
||||||
import {prepareLists} from './prepareLists'
|
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('Lists', () => {
|
|
||||||
let lists
|
|
||||||
prepareLists((newLists) => (lists = newLists))
|
|
||||||
|
|
||||||
it('Should create a new list', () => {
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('.namespace-title .dropdown-trigger')
|
|
||||||
.click()
|
|
||||||
cy.get('.namespace-title .dropdown .dropdown-item')
|
|
||||||
.contains('New list')
|
|
||||||
.click()
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/lists/new/1')
|
|
||||||
cy.get('.card-header-title')
|
|
||||||
.contains('New list')
|
|
||||||
cy.get('input.input')
|
|
||||||
.type('New List')
|
|
||||||
cy.get('.button')
|
|
||||||
.contains('Create')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new list is done
|
|
||||||
.should('contain', 'Success')
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/lists/')
|
|
||||||
cy.get('.list-title h1')
|
|
||||||
.should('contain', 'New List')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should redirect to a specific list view after visited', () => {
|
|
||||||
cy.visit('/lists/1/kanban')
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/lists/1/kanban')
|
|
||||||
cy.visit('/lists/1')
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/lists/1/kanban')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should rename the list in all places', () => {
|
|
||||||
TaskFactory.create(5, {
|
|
||||||
id: '{increment}',
|
|
||||||
list_id: 1,
|
|
||||||
})
|
|
||||||
const newListName = 'New list name'
|
|
||||||
|
|
||||||
cy.visit('/lists/1')
|
|
||||||
cy.get('.list-title h1')
|
|
||||||
.should('contain', 'First List')
|
|
||||||
|
|
||||||
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-trigger')
|
|
||||||
.click()
|
|
||||||
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-content')
|
|
||||||
.contains('Edit')
|
|
||||||
.click()
|
|
||||||
cy.get('#title')
|
|
||||||
.type(`{selectall}${newListName}`)
|
|
||||||
cy.get('footer.card-footer .button')
|
|
||||||
.contains('Save')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.global-notification')
|
|
||||||
.should('contain', 'Success')
|
|
||||||
cy.get('.list-title h1')
|
|
||||||
.should('contain', newListName)
|
|
||||||
.should('not.contain', lists[0].title)
|
|
||||||
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child')
|
|
||||||
.should('contain', newListName)
|
|
||||||
.should('not.contain', lists[0].title)
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('.card-content')
|
|
||||||
.should('contain', newListName)
|
|
||||||
.should('not.contain', lists[0].title)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should remove a list', () => {
|
|
||||||
cy.visit(`/lists/${lists[0].id}`)
|
|
||||||
|
|
||||||
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-trigger')
|
|
||||||
.click()
|
|
||||||
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-content')
|
|
||||||
.contains('Delete')
|
|
||||||
.click()
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/settings/delete')
|
|
||||||
cy.get('[data-cy="modalPrimary"]')
|
|
||||||
.contains('Do it')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.global-notification')
|
|
||||||
.should('contain', 'Success')
|
|
||||||
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
|
|
||||||
.should('not.contain', lists[0].title)
|
|
||||||
cy.location('pathname')
|
|
||||||
.should('equal', '/')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should archive a list', () => {
|
|
||||||
cy.visit(`/lists/${lists[0].id}`)
|
|
||||||
|
|
||||||
cy.get('.list-title .dropdown')
|
|
||||||
.click()
|
|
||||||
cy.get('.list-title .dropdown .dropdown-menu .dropdown-item')
|
|
||||||
.contains('Archive')
|
|
||||||
.click()
|
|
||||||
cy.get('.modal-content')
|
|
||||||
.should('contain.text', 'Archive this list')
|
|
||||||
cy.get('.modal-content [data-cy=modalPrimary]')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
|
|
||||||
.should('not.contain', lists[0].title)
|
|
||||||
cy.get('main.app-content')
|
|
||||||
.should('contain.text', 'This list is archived. It is not possible to create new or edit tasks for it.')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,21 +0,0 @@
|
||||||
import {ListFactory} from '../../factories/list'
|
|
||||||
import {UserFactory} from '../../factories/user'
|
|
||||||
import {NamespaceFactory} from '../../factories/namespace'
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
|
||||||
|
|
||||||
export function createLists() {
|
|
||||||
UserFactory.create(1)
|
|
||||||
NamespaceFactory.create(1)
|
|
||||||
const lists = ListFactory.create(1, {
|
|
||||||
title: 'First List'
|
|
||||||
})
|
|
||||||
TaskFactory.truncate()
|
|
||||||
return lists
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prepareLists(setLists = () => {}) {
|
|
||||||
beforeEach(() => {
|
|
||||||
const lists = createLists()
|
|
||||||
setLists(lists)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
import {ListFactory} from '../../factories/list'
|
|
||||||
import {seed} from '../../support/seed'
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
import {UserFactory} from '../../factories/user'
|
|
||||||
import {NamespaceFactory} from '../../factories/namespace'
|
|
||||||
import {BucketFactory} from '../../factories/bucket'
|
|
||||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
|
||||||
UserFactory.create(1)
|
|
||||||
NamespaceFactory.create(1)
|
|
||||||
const list = ListFactory.create()[0]
|
|
||||||
BucketFactory.create(1, {
|
|
||||||
list_id: list.id,
|
|
||||||
})
|
|
||||||
const tasks = []
|
|
||||||
let dueDate = startDueDate
|
|
||||||
for (let i = 0; i < numberOfTasks; i++) {
|
|
||||||
const now = new Date()
|
|
||||||
dueDate = (new Date(dueDate.valueOf())).setDate((new Date(dueDate.valueOf())).getDate() + 2)
|
|
||||||
tasks.push({
|
|
||||||
id: i + 1,
|
|
||||||
list_id: list.id,
|
|
||||||
done: false,
|
|
||||||
created_by_id: 1,
|
|
||||||
title: 'Test Task ' + i,
|
|
||||||
index: i + 1,
|
|
||||||
due_date: formatISO(dueDate),
|
|
||||||
created: formatISO(now),
|
|
||||||
updated: formatISO(now),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
seed(TaskFactory.table, tasks)
|
|
||||||
return {tasks, list}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Home Page Task Overview', () => {
|
|
||||||
it('Should show tasks with a near due date first on the home page overview', () => {
|
|
||||||
const {tasks} = seedTasks()
|
|
||||||
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
|
||||||
.each(([task], index) => {
|
|
||||||
expect(task.innerText).to.contain(tasks[index].title)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should show overdue tasks first, then show other tasks', () => {
|
|
||||||
const oldDate = (new Date()).setDate((new Date()).getDate() - 14)
|
|
||||||
const {tasks} = seedTasks(100, oldDate)
|
|
||||||
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
|
||||||
.each(([task], index) => {
|
|
||||||
expect(task.innerText).to.contain(tasks[index].title)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should show a new task with a very soon due date at the top', () => {
|
|
||||||
const {tasks} = seedTasks()
|
|
||||||
const newTaskTitle = 'New Task'
|
|
||||||
|
|
||||||
cy.visit('/')
|
|
||||||
|
|
||||||
TaskFactory.create(1, {
|
|
||||||
id: 999,
|
|
||||||
title: newTaskTitle,
|
|
||||||
due_date: formatISO(new Date()),
|
|
||||||
}, false)
|
|
||||||
|
|
||||||
cy.visit(`/lists/${tasks[0].list_id}/list`)
|
|
||||||
cy.get('.tasks .task')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', newTaskTitle)
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', newTaskTitle)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not show a new task without a date at the bottom when there are > 50 tasks', () => {
|
|
||||||
// We're not using the api here to create the task in order to verify the flow
|
|
||||||
const {tasks} = seedTasks()
|
|
||||||
const newTaskTitle = 'New Task'
|
|
||||||
|
|
||||||
cy.visit('/')
|
|
||||||
|
|
||||||
cy.visit(`/lists/${tasks[0].list_id}/list`)
|
|
||||||
cy.get('.task-add textarea')
|
|
||||||
.type(newTaskTitle+'{enter}')
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
|
||||||
.last()
|
|
||||||
.should('not.contain.text', newTaskTitle)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should show a new task without a date at the bottom when there are < 50 tasks', () => {
|
|
||||||
seedTasks(40)
|
|
||||||
const newTaskTitle = 'New Task'
|
|
||||||
TaskFactory.create(1, {
|
|
||||||
id: 999,
|
|
||||||
title: newTaskTitle,
|
|
||||||
}, false)
|
|
||||||
|
|
||||||
cy.visit('/')
|
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
|
||||||
.last()
|
|
||||||
.should('contain.text', newTaskTitle)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should show a task without a due date added via default list at the bottom', () => {
|
|
||||||
const {list} = seedTasks(40)
|
|
||||||
updateUserSettings({
|
|
||||||
default_list_id: list.id,
|
|
||||||
overdue_tasks_reminders_time: '9:00',
|
|
||||||
})
|
|
||||||
|
|
||||||
const newTaskTitle = 'New Task'
|
|
||||||
cy.visit('/')
|
|
||||||
|
|
||||||
cy.get('.add-task-textarea')
|
|
||||||
.type(`${newTaskTitle}{enter}`)
|
|
||||||
|
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
|
||||||
.last()
|
|
||||||
.should('contain.text', newTaskTitle)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,44 +0,0 @@
|
||||||
import '../../support/authenticateUser'
|
|
||||||
import {createLists} from '../list/prepareLists'
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
cy.get('.navbar .user .username')
|
|
||||||
.click()
|
|
||||||
cy.get('.navbar .user .dropdown-menu .dropdown-item')
|
|
||||||
.contains('Logout')
|
|
||||||
.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Log out', () => {
|
|
||||||
it('Logs the user out', () => {
|
|
||||||
cy.visit('/')
|
|
||||||
|
|
||||||
expect(localStorage.getItem('token')).to.not.eq(null)
|
|
||||||
|
|
||||||
logout()
|
|
||||||
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/login')
|
|
||||||
.then(() => {
|
|
||||||
expect(localStorage.getItem('token')).to.eq(null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it.skip('Should clear the list history after logging the user out', () => {
|
|
||||||
const lists = createLists()
|
|
||||||
cy.visit(`/lists/${lists[0].id}`)
|
|
||||||
.then(() => {
|
|
||||||
expect(localStorage.getItem('listHistory')).to.not.eq(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
logout()
|
|
||||||
|
|
||||||
cy.wait(1000) // This makes re-loading of the list and associated entities (and the resulting error) visible
|
|
||||||
|
|
||||||
cy.url()
|
|
||||||
.should('contain', '/login')
|
|
||||||
.then(() => {
|
|
||||||
expect(localStorage.getItem('listHistory')).to.eq(null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
|
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
|
@ -1,6 +1,6 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
import {formatISO} from "date-fns"
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
|
|
||||||
export class LinkShareFactory extends Factory {
|
export class LinkShareFactory extends Factory {
|
||||||
static table = 'link_shares'
|
static table = 'link_shares'
|
|
@ -1,6 +1,6 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
import {formatISO} from "date-fns"
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
|
|
||||||
export class ListFactory extends Factory {
|
export class ListFactory extends Factory {
|
||||||
static table = 'lists'
|
static table = 'lists'
|
|
@ -1,4 +1,4 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ export class TaskFactory extends Factory {
|
||||||
list_id: 1,
|
list_id: 1,
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
index: '{increment}',
|
index: '{increment}',
|
||||||
position: '{increment}',
|
|
||||||
created: formatISO(now),
|
created: formatISO(now),
|
||||||
updated: formatISO(now)
|
updated: formatISO(now)
|
||||||
}
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
import {Factory} from '../support/factory'
|
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class TaskAttachmentFactory extends Factory {
|
|
||||||
static table = 'task_attachments'
|
|
||||||
|
|
||||||
static factory() {
|
|
||||||
const now = new Date()
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: '{increment}',
|
|
||||||
task_id: 1,
|
|
||||||
file_id: 1,
|
|
||||||
created: formatISO(now),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
|
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
import {formatISO} from "date-fns"
|
|
@ -1,4 +1,4 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import faker from 'faker'
|
||||||
|
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
import {formatISO} from "date-fns"
|
||||||
|
@ -14,7 +14,6 @@ export class UserFactory extends Factory {
|
||||||
username: faker.lorem.word(10) + faker.datatype.uuid(),
|
username: faker.lorem.word(10) + faker.datatype.uuid(),
|
||||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
|
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
|
||||||
status: 0,
|
status: 0,
|
||||||
issuer: 'local',
|
|
||||||
created: formatISO(now),
|
created: formatISO(now),
|
||||||
updated: formatISO(now)
|
updated: formatISO(now)
|
||||||
}
|
}
|
540
cypress/integration/list/list.spec.js
Normal file
|
@ -0,0 +1,540 @@
|
||||||
|
import {formatISO, format} from 'date-fns'
|
||||||
|
|
||||||
|
import {TaskFactory} from '../../factories/task'
|
||||||
|
import {ListFactory} from '../../factories/list'
|
||||||
|
import {UserListFactory} from '../../factories/users_list'
|
||||||
|
import {UserFactory} from '../../factories/user'
|
||||||
|
import {NamespaceFactory} from '../../factories/namespace'
|
||||||
|
import {BucketFactory} from '../../factories/bucket'
|
||||||
|
|
||||||
|
import '../../support/authenticateUser'
|
||||||
|
|
||||||
|
describe('Lists', () => {
|
||||||
|
let lists
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
UserFactory.create(1)
|
||||||
|
NamespaceFactory.create(1)
|
||||||
|
lists = ListFactory.create(1, {
|
||||||
|
title: 'First List'
|
||||||
|
})
|
||||||
|
TaskFactory.truncate()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should create a new list', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.get('.namespace-title .dropdown-trigger')
|
||||||
|
.click()
|
||||||
|
cy.get('.namespace-title .dropdown .dropdown-item')
|
||||||
|
.contains('New list')
|
||||||
|
.click()
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '/namespaces/1/list')
|
||||||
|
cy.get('.card-header-title')
|
||||||
|
.contains('New list')
|
||||||
|
cy.get('input.input')
|
||||||
|
.type('New List')
|
||||||
|
cy.get('.button')
|
||||||
|
.contains('Create')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new list is done
|
||||||
|
.should('contain', 'Success')
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '/lists/')
|
||||||
|
cy.get('.list-title h1')
|
||||||
|
.should('contain', 'New List')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should redirect to a specific list view after visited', () => {
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '/lists/1/kanban')
|
||||||
|
cy.visit('/lists/1')
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '/lists/1/kanban')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should rename the list in all places', () => {
|
||||||
|
const tasks = TaskFactory.create(5, {
|
||||||
|
id: '{increment}',
|
||||||
|
list_id: 1,
|
||||||
|
})
|
||||||
|
const newListName = 'New list name'
|
||||||
|
|
||||||
|
cy.visit('/lists/1')
|
||||||
|
cy.get('.list-title h1')
|
||||||
|
.should('contain', 'First List')
|
||||||
|
|
||||||
|
cy.get('.namespace-container .menu.namespaces-lists .more-container .menu-list li:first-child .dropdown .dropdown-trigger')
|
||||||
|
.click()
|
||||||
|
cy.get('.namespace-container .menu.namespaces-lists .more-container .menu-list li:first-child .dropdown .dropdown-content')
|
||||||
|
.contains('Edit')
|
||||||
|
.click()
|
||||||
|
cy.get('#title')
|
||||||
|
.type(`{selectall}${newListName}`)
|
||||||
|
cy.get('footer.modal-card-foot .button')
|
||||||
|
.contains('Save')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.global-notification')
|
||||||
|
.should('contain', 'Success')
|
||||||
|
cy.get('.list-title h1')
|
||||||
|
.should('contain', newListName)
|
||||||
|
.should('not.contain', lists[0].title)
|
||||||
|
cy.get('.namespace-container .menu.namespaces-lists .more-container .menu-list li:first-child')
|
||||||
|
.should('contain', newListName)
|
||||||
|
.should('not.contain', lists[0].title)
|
||||||
|
cy.visit('/')
|
||||||
|
cy.get('.card-content .tasks')
|
||||||
|
.should('contain', newListName)
|
||||||
|
.should('not.contain', lists[0].title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should remove a list', () => {
|
||||||
|
cy.visit(`/lists/${lists[0].id}`)
|
||||||
|
|
||||||
|
cy.get('.namespace-container .menu.namespaces-lists .more-container .menu-list li:first-child .dropdown .dropdown-trigger')
|
||||||
|
.click()
|
||||||
|
cy.get('.namespace-container .menu.namespaces-lists .more-container .menu-list li:first-child .dropdown .dropdown-content')
|
||||||
|
.contains('Delete')
|
||||||
|
.click()
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '/settings/delete')
|
||||||
|
cy.get('[data-cy="modalPrimary"]')
|
||||||
|
.contains('Do it')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.global-notification')
|
||||||
|
.should('contain', 'Success')
|
||||||
|
cy.get('.namespace-container .menu.namespaces-lists .more-container .menu-list')
|
||||||
|
.should('not.contain', lists[0].title)
|
||||||
|
cy.location('pathname')
|
||||||
|
.should('equal', '/')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('List View', () => {
|
||||||
|
it('Should be an empty list', () => {
|
||||||
|
cy.visit('/lists/1')
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '/lists/1/list')
|
||||||
|
cy.get('.list-title h1')
|
||||||
|
.should('contain', 'First List')
|
||||||
|
cy.get('.list-title .dropdown')
|
||||||
|
.should('exist')
|
||||||
|
cy.get('p')
|
||||||
|
.contains('This list is currently empty.')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should navigate to the task when the title is clicked', () => {
|
||||||
|
const tasks = TaskFactory.create(5, {
|
||||||
|
id: '{increment}',
|
||||||
|
list_id: 1,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/list')
|
||||||
|
|
||||||
|
cy.get('.tasks .task .tasktext')
|
||||||
|
.contains(tasks[0].title)
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.url()
|
||||||
|
.should('contain', `/tasks/${tasks[0].id}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not see any elements for a list which is shared read only', () => {
|
||||||
|
UserFactory.create(2)
|
||||||
|
UserListFactory.create(1, {
|
||||||
|
list_id: 2,
|
||||||
|
user_id: 1,
|
||||||
|
right: 0,
|
||||||
|
})
|
||||||
|
const lists = ListFactory.create(2, {
|
||||||
|
owner_id: '{increment}',
|
||||||
|
namespace_id: '{increment}',
|
||||||
|
})
|
||||||
|
cy.visit(`/lists/${lists[1].id}/`)
|
||||||
|
|
||||||
|
cy.get('.list-title a.icon')
|
||||||
|
.should('not.exist')
|
||||||
|
cy.get('input.input[placeholder="Add a new task..."')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should only show the color of a list in the navigation and not in the list view', () => {
|
||||||
|
const lists = ListFactory.create(1, {
|
||||||
|
hex_color: '00db60',
|
||||||
|
})
|
||||||
|
TaskFactory.create(10, {
|
||||||
|
list_id: lists[0].id,
|
||||||
|
})
|
||||||
|
cy.visit(`/lists/${lists[0].id}/`)
|
||||||
|
|
||||||
|
cy.get('.menu-list li .list-menu-link .color-bubble')
|
||||||
|
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
|
||||||
|
cy.get('.tasks-container .tasks .color-bubble')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should paginate for > 50 tasks', () => {
|
||||||
|
const tasks = TaskFactory.create(100, {
|
||||||
|
id: '{increment}',
|
||||||
|
title: i => `task${i}`,
|
||||||
|
list_id: 1,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/list')
|
||||||
|
|
||||||
|
cy.get('.tasks-container .tasks')
|
||||||
|
.should('contain', tasks[99].title)
|
||||||
|
|
||||||
|
cy.get('.card-content .pagination .pagination-link')
|
||||||
|
.contains('2')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '?page=2')
|
||||||
|
cy.get('.tasks-container .tasks')
|
||||||
|
.should('contain', tasks[1].title)
|
||||||
|
cy.get('.tasks-container .tasks')
|
||||||
|
.should('not.contain', tasks[99].title)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Table View', () => {
|
||||||
|
it('Should show a table with tasks', () => {
|
||||||
|
const tasks = TaskFactory.create(1)
|
||||||
|
cy.visit('/lists/1/table')
|
||||||
|
|
||||||
|
cy.get('.table-view table.table')
|
||||||
|
.should('exist')
|
||||||
|
cy.get('.table-view table.table')
|
||||||
|
.should('contain', tasks[0].title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have working column switches', () => {
|
||||||
|
TaskFactory.create(1)
|
||||||
|
cy.visit('/lists/1/table')
|
||||||
|
|
||||||
|
cy.get('.table-view .filter-container .items .button')
|
||||||
|
.contains('Columns')
|
||||||
|
.click()
|
||||||
|
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||||
|
.contains('Priority')
|
||||||
|
.click()
|
||||||
|
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||||
|
.contains('Done')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.table-view table.table th')
|
||||||
|
.contains('Priority')
|
||||||
|
.should('exist')
|
||||||
|
cy.get('.table-view table.table th')
|
||||||
|
.contains('Done')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should navigate to the task when the title is clicked', () => {
|
||||||
|
const tasks = TaskFactory.create(5, {
|
||||||
|
id: '{increment}',
|
||||||
|
list_id: 1,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/table')
|
||||||
|
|
||||||
|
cy.get('.table-view table.table')
|
||||||
|
.contains(tasks[0].title)
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.url()
|
||||||
|
.should('contain', `/tasks/${tasks[0].id}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Gantt View', () => {
|
||||||
|
it('Hides tasks with no dates', () => {
|
||||||
|
const tasks = TaskFactory.create(1)
|
||||||
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
|
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||||
|
.should('not.contain', tasks[0].title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Shows tasks from the current and next month', () => {
|
||||||
|
const now = new Date()
|
||||||
|
const nextMonth = now
|
||||||
|
nextMonth.setDate(1)
|
||||||
|
nextMonth.setMonth(now.getMonth() + 1)
|
||||||
|
|
||||||
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
|
cy.get('.gantt-chart-container .gantt-chart .months')
|
||||||
|
.should('contain', format(now, 'MMMM'))
|
||||||
|
.should('contain', format(nextMonth, 'MMMM'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Shows tasks with dates', () => {
|
||||||
|
const now = new Date()
|
||||||
|
const tasks = TaskFactory.create(1, {
|
||||||
|
start_date: formatISO(now),
|
||||||
|
end_date: formatISO(now.setDate(now.getDate() + 4))
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
|
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||||
|
.should('not.be.empty')
|
||||||
|
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||||
|
.should('contain', tasks[0].title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Shows tasks with no dates after enabling them', () => {
|
||||||
|
TaskFactory.create(1, {
|
||||||
|
start_date: null,
|
||||||
|
end_date: null,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
|
cy.get('.gantt-chart-container .gantt-options .fancycheckbox')
|
||||||
|
.contains('Show tasks which don\'t have dates set')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.gantt-chart-container .gantt-chart .tasks')
|
||||||
|
.should('not.be.empty')
|
||||||
|
cy.get('.gantt-chart-container .gantt-chart .tasks .task.nodate')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Drags a task around', () => {
|
||||||
|
const now = new Date()
|
||||||
|
TaskFactory.create(1, {
|
||||||
|
start_date: formatISO(now),
|
||||||
|
end_date: formatISO(now.setDate(now.getDate() + 4))
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
|
cy.get('.gantt-chart-container .gantt-chart .tasks .task')
|
||||||
|
.first()
|
||||||
|
.trigger('mousedown', {which: 1})
|
||||||
|
.trigger('mousemove', {clientX: 500, clientY: 0})
|
||||||
|
.trigger('mouseup', {force: true})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Kanban', () => {
|
||||||
|
let buckets
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
buckets = BucketFactory.create(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Shows all buckets with their tasks', () => {
|
||||||
|
const data = TaskFactory.create(10, {
|
||||||
|
list_id: 1,
|
||||||
|
bucket_id: 1,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket .title')
|
||||||
|
.contains(buckets[0].title)
|
||||||
|
.should('exist')
|
||||||
|
cy.get('.kanban .bucket .title')
|
||||||
|
.contains(buckets[1].title)
|
||||||
|
.should('exist')
|
||||||
|
cy.get('.kanban .bucket')
|
||||||
|
.first()
|
||||||
|
.should('contain', data[0].title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can add a new task to a bucket', () => {
|
||||||
|
const data = TaskFactory.create(2, {
|
||||||
|
list_id: 1,
|
||||||
|
bucket_id: 1,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket')
|
||||||
|
.contains(buckets[0].title)
|
||||||
|
.get('.bucket-footer .button')
|
||||||
|
.contains('Add another task')
|
||||||
|
.click()
|
||||||
|
cy.get('.kanban .bucket')
|
||||||
|
.contains(buckets[0].title)
|
||||||
|
.get('.bucket-footer .field .control input.input')
|
||||||
|
.type('New Task{enter}')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket')
|
||||||
|
.first()
|
||||||
|
.should('contain', 'New Task')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can create a new bucket', () => {
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket.new-bucket .button')
|
||||||
|
.click()
|
||||||
|
cy.get('.kanban .bucket.new-bucket input.input')
|
||||||
|
.type('New Bucket{enter}')
|
||||||
|
|
||||||
|
cy.wait(1000) // Wait for the request to finish
|
||||||
|
cy.get('.kanban .bucket .title')
|
||||||
|
.contains('New Bucket')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can set a bucket limit', () => {
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||||
|
.contains('Limit: Not Set')
|
||||||
|
.click()
|
||||||
|
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
|
||||||
|
.first()
|
||||||
|
.type(3)
|
||||||
|
cy.get('[data-cy="setBucketLimit"]')
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket .bucket-header span.limit')
|
||||||
|
.contains('0/3')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can rename a bucket', () => {
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket .bucket-header .title')
|
||||||
|
.first()
|
||||||
|
.type('{selectall}New Bucket Title{enter}')
|
||||||
|
cy.get('.kanban .bucket .bucket-header .title')
|
||||||
|
.first()
|
||||||
|
.should('contain', 'New Bucket Title')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can delete a bucket', () => {
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||||
|
.contains('Delete')
|
||||||
|
.click()
|
||||||
|
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||||
|
.should('contain', 'Delete the bucket')
|
||||||
|
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
||||||
|
.contains('Do it!')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket .title')
|
||||||
|
.contains(buckets[0].title)
|
||||||
|
.should('not.exist')
|
||||||
|
cy.get('.kanban .bucket .title')
|
||||||
|
.contains(buckets[1].title)
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Can drag tasks around', () => {
|
||||||
|
const tasks = TaskFactory.create(2, {
|
||||||
|
list_id: 1,
|
||||||
|
bucket_id: 1,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket .tasks .task')
|
||||||
|
.contains(tasks[0].title)
|
||||||
|
.first()
|
||||||
|
.drag('.kanban .bucket:nth-child(2) .tasks .dropper')
|
||||||
|
|
||||||
|
cy.get('.kanban .bucket:nth-child(2) .tasks')
|
||||||
|
.should('contain', tasks[0].title)
|
||||||
|
cy.get('.kanban .bucket:nth-child(1) .tasks')
|
||||||
|
.should('not.contain', tasks[0].title)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should navigate to the task when the task card is clicked', () => {
|
||||||
|
const tasks = TaskFactory.create(5, {
|
||||||
|
id: '{increment}',
|
||||||
|
list_id: 1,
|
||||||
|
bucket_id: 1,
|
||||||
|
})
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.getSettled('.kanban .bucket .tasks .task')
|
||||||
|
.contains(tasks[0].title)
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.url()
|
||||||
|
.should('contain', `/tasks/${tasks[0].id}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should remove a task from the kanban board when moving it to another list', () => {
|
||||||
|
const lists = ListFactory.create(2)
|
||||||
|
BucketFactory.create(2, {
|
||||||
|
list_id: '{increment}',
|
||||||
|
})
|
||||||
|
const tasks = TaskFactory.create(5, {
|
||||||
|
id: '{increment}',
|
||||||
|
list_id: 1,
|
||||||
|
bucket_id: 1,
|
||||||
|
})
|
||||||
|
const task = tasks[0]
|
||||||
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
|
cy.getSettled('.kanban .bucket .tasks .task')
|
||||||
|
.contains(task.title)
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.task-view .action-buttons .button')
|
||||||
|
.contains('Move task')
|
||||||
|
.click()
|
||||||
|
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||||
|
.type(`${lists[1].title}{enter}`)
|
||||||
|
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
|
||||||
|
// presses enter and we can't simulate pressing on enter to select the item.
|
||||||
|
cy.get('.task-view .content.details .field .multiselect.control .search-results')
|
||||||
|
.children()
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.global-notification', { timeout: 1000 })
|
||||||
|
.should('contain', 'Success')
|
||||||
|
cy.go('back')
|
||||||
|
cy.get('.kanban .bucket')
|
||||||
|
.should('not.contain', task.title)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('List history', () => {
|
||||||
|
it('should show a list history on the home page', () => {
|
||||||
|
const lists = ListFactory.create(6)
|
||||||
|
|
||||||
|
cy.visit('/')
|
||||||
|
cy.get('h3')
|
||||||
|
.contains('Last viewed')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.visit(`/lists/${lists[0].id}`)
|
||||||
|
cy.visit(`/lists/${lists[1].id}`)
|
||||||
|
cy.visit(`/lists/${lists[2].id}`)
|
||||||
|
cy.visit(`/lists/${lists[3].id}`)
|
||||||
|
cy.visit(`/lists/${lists[4].id}`)
|
||||||
|
cy.visit(`/lists/${lists[5].id}`)
|
||||||
|
|
||||||
|
cy.visit('/')
|
||||||
|
cy.get('h3')
|
||||||
|
.contains('Last viewed')
|
||||||
|
.should('exist')
|
||||||
|
cy.get('.list-cards-wrapper-2-rows')
|
||||||
|
.should('not.contain', lists[0].title)
|
||||||
|
.should('contain', lists[1].title)
|
||||||
|
.should('contain', lists[2].title)
|
||||||
|
.should('contain', lists[3].title)
|
||||||
|
.should('contain', lists[4].title)
|
||||||
|
.should('contain', lists[5].title)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -63,7 +63,7 @@ describe('Namepaces', () => {
|
||||||
.should('equal', newNamespaces[0].title) // wait until the namespace data is loaded
|
.should('equal', newNamespaces[0].title) // wait until the namespace data is loaded
|
||||||
cy.get('#namespacetext')
|
cy.get('#namespacetext')
|
||||||
.type(`{selectall}${newNamespaceName}`)
|
.type(`{selectall}${newNamespaceName}`)
|
||||||
cy.get('footer.card-footer .button')
|
cy.get('footer.modal-card-foot .button')
|
||||||
.contains('Save')
|
.contains('Save')
|
||||||
.click()
|
.click()
|
||||||
|
|
|
@ -6,57 +6,21 @@ import {TaskCommentFactory} from '../../factories/task_comment'
|
||||||
import {UserFactory} from '../../factories/user'
|
import {UserFactory} from '../../factories/user'
|
||||||
import {NamespaceFactory} from '../../factories/namespace'
|
import {NamespaceFactory} from '../../factories/namespace'
|
||||||
import {UserListFactory} from '../../factories/users_list'
|
import {UserListFactory} from '../../factories/users_list'
|
||||||
|
|
||||||
|
import '../../support/authenticateUser'
|
||||||
import {TaskAssigneeFactory} from '../../factories/task_assignee'
|
import {TaskAssigneeFactory} from '../../factories/task_assignee'
|
||||||
import {LabelFactory} from '../../factories/labels'
|
import {LabelFactory} from '../../factories/labels'
|
||||||
import {LabelTaskFactory} from '../../factories/label_task'
|
import {LabelTaskFactory} from '../../factories/label_task'
|
||||||
import {BucketFactory} from '../../factories/bucket'
|
import {BucketFactory} from '../../factories/bucket'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
import {TaskAttachmentFactory} from '../../factories/task_attachments'
|
|
||||||
|
|
||||||
function addLabelToTaskAndVerify(labelTitle: string) {
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
|
||||||
.contains('Add Labels')
|
|
||||||
.click()
|
|
||||||
cy.get('.task-view .details.labels-list .multiselect input')
|
|
||||||
.type(labelTitle)
|
|
||||||
cy.get('.task-view .details.labels-list .multiselect .search-results')
|
|
||||||
.children()
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.global-notification', { timeout: 4000 })
|
|
||||||
.should('contain', 'Success')
|
|
||||||
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
|
|
||||||
.should('exist')
|
|
||||||
.should('contain', labelTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadAttachmentAndVerify(taskId: number) {
|
|
||||||
cy.intercept(`${Cypress.env('API_URL')}/tasks/${taskId}/attachments`).as('uploadAttachment')
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
|
||||||
.contains('Add Attachments')
|
|
||||||
.click()
|
|
||||||
cy.get('input[type=file]', {timeout: 1000})
|
|
||||||
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
|
||||||
cy.wait('@uploadAttachment')
|
|
||||||
|
|
||||||
cy.get('.attachments .attachments .files a.attachment')
|
|
||||||
.should('exist')
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Task', () => {
|
describe('Task', () => {
|
||||||
let namespaces
|
let namespaces
|
||||||
let lists
|
let lists
|
||||||
let buckets
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserFactory.create(1)
|
UserFactory.create(1)
|
||||||
namespaces = NamespaceFactory.create(1)
|
namespaces = NamespaceFactory.create(1)
|
||||||
lists = ListFactory.create(1)
|
lists = ListFactory.create(1)
|
||||||
buckets = BucketFactory.create(1, {
|
|
||||||
list_id: lists[0].id,
|
|
||||||
})
|
|
||||||
TaskFactory.truncate()
|
TaskFactory.truncate()
|
||||||
UserListFactory.truncate()
|
UserListFactory.truncate()
|
||||||
})
|
})
|
||||||
|
@ -116,7 +80,6 @@ describe('Task', () => {
|
||||||
describe('Task Detail View', () => {
|
describe('Task Detail View', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TaskCommentFactory.truncate()
|
TaskCommentFactory.truncate()
|
||||||
LabelTaskFactory.truncate()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Shows all task details', () => {
|
it('Shows all task details', () => {
|
||||||
|
@ -153,7 +116,6 @@ describe('Task', () => {
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.should('contain', 'Done')
|
.should('contain', 'Done')
|
||||||
cy.get('.task-view .action-buttons p.created')
|
cy.get('.task-view .action-buttons p.created')
|
||||||
.scrollIntoView()
|
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.should('contain', 'Done')
|
.should('contain', 'Done')
|
||||||
})
|
})
|
||||||
|
@ -202,7 +164,7 @@ describe('Task', () => {
|
||||||
})
|
})
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
cy.get('.task-view .details.content.description .editor button')
|
cy.get('.task-view .details.content.description .editor a')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
|
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
|
||||||
.type('{selectall}New Description')
|
.type('{selectall}New Description')
|
||||||
|
@ -247,7 +209,7 @@ describe('Task', () => {
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
cy.get('.task-view .action-buttons .button')
|
||||||
.contains('Move')
|
.contains('Move task')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||||
.type(`${lists[1].title}{enter}`)
|
.type(`${lists[1].title}{enter}`)
|
||||||
|
@ -274,7 +236,7 @@ describe('Task', () => {
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
cy.get('.task-view .action-buttons .button')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.contains('Delete')
|
.contains('Delete task')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||||
.should('contain', 'Delete this task')
|
.should('contain', 'Delete this task')
|
||||||
|
@ -334,7 +296,7 @@ describe('Task', () => {
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
|
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
|
||||||
.get('.remove-assignee')
|
.get('a.remove-assignee')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
cy.get('.global-notification')
|
cy.get('.global-notification')
|
||||||
|
@ -354,7 +316,7 @@ describe('Task', () => {
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
cy.get('.task-view .action-buttons .button')
|
||||||
.contains('Add Labels')
|
.contains('Add labels')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.task-view .details.labels-list .multiselect input')
|
cy.get('.task-view .details.labels-list .multiselect input')
|
||||||
|
@ -377,35 +339,24 @@ describe('Task', () => {
|
||||||
list_id: 1,
|
list_id: 1,
|
||||||
})
|
})
|
||||||
const labels = LabelFactory.create(1)
|
const labels = LabelFactory.create(1)
|
||||||
LabelTaskFactory.truncate()
|
|
||||||
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
addLabelToTaskAndVerify(labels[0].title)
|
cy.get('.task-view .action-buttons .button')
|
||||||
})
|
.contains('Add labels')
|
||||||
|
.click()
|
||||||
it('Can add a label to a task and it shows up on the kanban board afterwards', () => {
|
cy.get('.task-view .details.labels-list .multiselect input')
|
||||||
const tasks = TaskFactory.create(1, {
|
.type(labels[0].title)
|
||||||
id: 1,
|
cy.get('.task-view .details.labels-list .multiselect .search-results')
|
||||||
list_id: lists[0].id,
|
.children()
|
||||||
bucket_id: buckets[0].id,
|
.first()
|
||||||
})
|
|
||||||
const labels = LabelFactory.create(1)
|
|
||||||
LabelTaskFactory.truncate()
|
|
||||||
|
|
||||||
cy.visit(`/lists/${lists[0].id}/kanban`)
|
|
||||||
|
|
||||||
cy.get('.bucket .task')
|
|
||||||
.contains(tasks[0].title)
|
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
addLabelToTaskAndVerify(labels[0].title)
|
cy.get('.global-notification', { timeout: 4000 })
|
||||||
|
.should('contain', 'Success')
|
||||||
cy.get('.modal-content .close')
|
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
|
||||||
.click()
|
.should('exist')
|
||||||
|
.should('contain', labels[0].title)
|
||||||
cy.get('.bucket .task')
|
|
||||||
.should('contain.text', labels[0].title)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can remove a label from a task', () => {
|
it('Can remove a label from a task', () => {
|
||||||
|
@ -421,13 +372,13 @@ describe('Task', () => {
|
||||||
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.should('contain', labels[0].title)
|
.should('contain', labels[0].title)
|
||||||
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||||
.children()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.get('[data-cy="taskDetail.removeLabel"]')
|
.get('a.delete')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
cy.get('.global-notification')
|
cy.get('.global-notification')
|
||||||
|
@ -450,7 +401,7 @@ describe('Task', () => {
|
||||||
.contains('Due Date')
|
.contains('Due Date')
|
||||||
.get('.date-input .datepicker .show')
|
.get('.date-input .datepicker .show')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.datepicker .datepicker-popup button')
|
cy.get('.datepicker .datepicker-popup a')
|
||||||
.contains('Tomorrow')
|
.contains('Tomorrow')
|
||||||
.click()
|
.click()
|
||||||
cy.get('[data-cy="closeDatepicker"]')
|
cy.get('[data-cy="closeDatepicker"]')
|
||||||
|
@ -464,87 +415,5 @@ describe('Task', () => {
|
||||||
cy.get('.global-notification')
|
cy.get('.global-notification')
|
||||||
.should('contain', 'Success')
|
.should('contain', 'Success')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can set a priority for a task', () => {
|
|
||||||
const tasks = TaskFactory.create(1, {
|
|
||||||
id: 1,
|
|
||||||
})
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
|
||||||
.contains('Set Priority')
|
|
||||||
.click()
|
|
||||||
cy.get('.task-view .columns.details .column')
|
|
||||||
.contains('Priority')
|
|
||||||
.get('.select select')
|
|
||||||
.select('Urgent')
|
|
||||||
cy.get('.global-notification')
|
|
||||||
.should('contain', 'Success')
|
|
||||||
|
|
||||||
cy.get('.task-view .columns.details .column')
|
|
||||||
.contains('Priority')
|
|
||||||
.get('.select select')
|
|
||||||
.should('have.value', '4')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can set the progress for a task', () => {
|
|
||||||
const tasks = TaskFactory.create(1, {
|
|
||||||
id: 1,
|
|
||||||
})
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
|
||||||
.contains('Set Progress')
|
|
||||||
.click()
|
|
||||||
cy.get('.task-view .columns.details .column')
|
|
||||||
.contains('Progress')
|
|
||||||
.get('.select select')
|
|
||||||
.select('50%')
|
|
||||||
cy.get('.global-notification')
|
|
||||||
.should('contain', 'Success')
|
|
||||||
|
|
||||||
cy.wait(200)
|
|
||||||
|
|
||||||
cy.get('.task-view .columns.details .column')
|
|
||||||
.contains('Progress')
|
|
||||||
.get('.select select')
|
|
||||||
.should('be.visible')
|
|
||||||
.should('have.value', '0.5')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can add an attachment to a task', () => {
|
|
||||||
TaskAttachmentFactory.truncate()
|
|
||||||
const tasks = TaskFactory.create(1, {
|
|
||||||
id: 1,
|
|
||||||
})
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
|
||||||
|
|
||||||
uploadAttachmentAndVerify(tasks[0].id)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Can add an attachment to a task and see it appearing on kanban', () => {
|
|
||||||
TaskAttachmentFactory.truncate()
|
|
||||||
const tasks = TaskFactory.create(1, {
|
|
||||||
id: 1,
|
|
||||||
list_id: lists[0].id,
|
|
||||||
bucket_id: buckets[0].id,
|
|
||||||
})
|
|
||||||
const labels = LabelFactory.create(1)
|
|
||||||
LabelTaskFactory.truncate()
|
|
||||||
|
|
||||||
cy.visit(`/lists/${lists[0].id}/kanban`)
|
|
||||||
|
|
||||||
cy.get('.bucket .task')
|
|
||||||
.contains(tasks[0].title)
|
|
||||||
.click()
|
|
||||||
|
|
||||||
uploadAttachmentAndVerify(tasks[0].id)
|
|
||||||
|
|
||||||
cy.get('.modal-content .close')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.get('.bucket .task .footer .icon svg.fa-paperclip')
|
|
||||||
.should('exist')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
16
cypress/integration/user/logout.spec.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import '../../support/authenticateUser'
|
||||||
|
|
||||||
|
describe('Log out', () => {
|
||||||
|
it('Logs the user out', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
|
||||||
|
cy.get('.navbar .user .username')
|
||||||
|
.click()
|
||||||
|
cy.get('.navbar .user .dropdown-menu a.dropdown-item')
|
||||||
|
.contains('Logout')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.url()
|
||||||
|
.should('contain', '/login')
|
||||||
|
})
|
||||||
|
})
|
|
@ -25,6 +25,7 @@ context('Registration', () => {
|
||||||
cy.get('#username').type(fixture.username)
|
cy.get('#username').type(fixture.username)
|
||||||
cy.get('#email').type(fixture.email)
|
cy.get('#email').type(fixture.email)
|
||||||
cy.get('#password').type(fixture.password)
|
cy.get('#password').type(fixture.password)
|
||||||
|
cy.get('#passwordValidation').type(fixture.password)
|
||||||
cy.get('#register-submit').click()
|
cy.get('#register-submit').click()
|
||||||
cy.url().should('include', '/')
|
cy.url().should('include', '/')
|
||||||
cy.clock(1625656161057) // 13:00
|
cy.clock(1625656161057) // 13:00
|
||||||
|
@ -42,6 +43,7 @@ context('Registration', () => {
|
||||||
cy.get('#username').type(fixture.username)
|
cy.get('#username').type(fixture.username)
|
||||||
cy.get('#email').type(fixture.email)
|
cy.get('#email').type(fixture.email)
|
||||||
cy.get('#password').type(fixture.password)
|
cy.get('#password').type(fixture.password)
|
||||||
|
cy.get('#passwordValidation').type(fixture.password)
|
||||||
cy.get('#register-submit').click()
|
cy.get('#register-submit').click()
|
||||||
cy.get('div.message.danger').contains('A user with this username already exists.')
|
cy.get('div.message.danger').contains('A user with this username already exists.')
|
||||||
})
|
})
|
|
@ -8,14 +8,12 @@ describe('User Settings', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Changes the user avatar', () => {
|
it('Changes the user avatar', () => {
|
||||||
cy.intercept(`${Cypress.env('API_URL')}/user/settings/avatar/upload`).as('uploadAvatar')
|
|
||||||
|
|
||||||
cy.visit('/user/settings/avatar')
|
cy.visit('/user/settings/avatar')
|
||||||
|
|
||||||
cy.get('input[name=avatarProvider][value=upload]')
|
cy.get('input[name=avatarProvider][value=upload]')
|
||||||
.click()
|
.click()
|
||||||
cy.get('input[type=file]', {timeout: 1000})
|
cy.get('input[type=file]', { timeout: 1000 })
|
||||||
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
.attachFile('image.jpg')
|
||||||
cy.get('.vue-handler-wrapper.vue-handler-wrapper--south .vue-simple-handler.vue-simple-handler--south')
|
cy.get('.vue-handler-wrapper.vue-handler-wrapper--south .vue-simple-handler.vue-simple-handler--south')
|
||||||
.trigger('mousedown', {which: 1})
|
.trigger('mousedown', {which: 1})
|
||||||
.trigger('mousemove', {clientY: 100})
|
.trigger('mousemove', {clientY: 100})
|
||||||
|
@ -24,7 +22,7 @@ describe('User Settings', () => {
|
||||||
.contains('Upload Avatar')
|
.contains('Upload Avatar')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
cy.wait('@uploadAvatar')
|
cy.wait(3000) // Wait for the request to finish
|
||||||
cy.get('.global-notification')
|
cy.get('.global-notification')
|
||||||
.should('contain', 'Success')
|
.should('contain', 'Success')
|
||||||
})
|
})
|
21
cypress/plugins/index.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************************
|
||||||
|
// This example plugins/index.js can be used to load plugins
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off loading
|
||||||
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/plugins-guide
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
|
// the project's config changing)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Cypress.PluginConfig}
|
||||||
|
*/
|
||||||
|
module.exports = (on, config) => {
|
||||||
|
// `on` is used to hook into various events Cypress emits
|
||||||
|
// `config` is the resolved Cypress config
|
||||||
|
}
|