Merge pull request 'Cleanup our dependencies' (#3572) from weight-removal into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3572
This commit is contained in:
commit
b90938c7bc
52 changed files with 2181 additions and 8557 deletions
5
.babelrc
5
.babelrc
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["@babel/preset-env"],
|
|
||||||
"plugins": ["@babel/plugin-transform-runtime", "@vue/babel-plugin-jsx"],
|
|
||||||
"comments": true
|
|
||||||
}
|
|
||||||
223
.gitlab-ci.yml
223
.gitlab-ci.yml
|
|
@ -1,223 +0,0 @@
|
||||||
# This file is a template, and might need editing before it works on your project.
|
|
||||||
# Official framework image. Look for the different tagged releases at:
|
|
||||||
# https://hub.docker.com/r/library/node/tags/
|
|
||||||
image: node:20
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- check-changelog
|
|
||||||
- lint
|
|
||||||
- build
|
|
||||||
- test
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
# https://git.pleroma.social/help/ci/yaml/workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines
|
|
||||||
workflow:
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
|
||||||
when: never
|
|
||||||
- if: $CI_COMMIT_BRANCH
|
|
||||||
|
|
||||||
check-changelog:
|
|
||||||
stage: check-changelog
|
|
||||||
image: alpine
|
|
||||||
rules:
|
|
||||||
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/
|
|
||||||
when: never
|
|
||||||
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate'
|
|
||||||
when: never
|
|
||||||
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"
|
|
||||||
before_script: ''
|
|
||||||
after_script: ''
|
|
||||||
cache: {}
|
|
||||||
script:
|
|
||||||
- apk add git
|
|
||||||
- sh ./tools/check-changelog
|
|
||||||
|
|
||||||
lint-eslint:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
- yarn
|
|
||||||
- yarn ci-eslint
|
|
||||||
|
|
||||||
lint-biome:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
- yarn
|
|
||||||
- yarn ci-biome
|
|
||||||
|
|
||||||
lint-stylelint:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
- yarn
|
|
||||||
- yarn ci-stylelint
|
|
||||||
|
|
||||||
test:
|
|
||||||
stage: test
|
|
||||||
tags:
|
|
||||||
- amd64
|
|
||||||
- himem
|
|
||||||
variables:
|
|
||||||
APT_CACHE_DIR: apt-cache
|
|
||||||
script:
|
|
||||||
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
|
||||||
- yarn
|
|
||||||
- yarn playwright install firefox
|
|
||||||
- yarn playwright install-deps
|
|
||||||
- yarn unit-ci
|
|
||||||
artifacts:
|
|
||||||
# When the test fails, upload screenshots for better context on why it fails
|
|
||||||
paths:
|
|
||||||
- test/**/__screenshots__
|
|
||||||
when: on_failure
|
|
||||||
|
|
||||||
e2e-pleroma:
|
|
||||||
stage: test
|
|
||||||
image: mcr.microsoft.com/playwright:v1.61.0-jammy
|
|
||||||
services:
|
|
||||||
- name: postgres:15-alpine
|
|
||||||
alias: db
|
|
||||||
- name: $PLEROMA_IMAGE
|
|
||||||
alias: pleroma
|
|
||||||
entrypoint: ["/bin/ash", "-c"]
|
|
||||||
command:
|
|
||||||
- |
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SEED_SENTINEL_PATH=/var/lib/pleroma/.e2e_seeded
|
|
||||||
CONFIG_OVERRIDE_PATH=/var/lib/pleroma/config.exs
|
|
||||||
|
|
||||||
echo '-- Waiting for database...'
|
|
||||||
while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma} -t 1; do
|
|
||||||
sleep 1s
|
|
||||||
done
|
|
||||||
|
|
||||||
echo '-- Writing E2E config overrides...'
|
|
||||||
cat > $CONFIG_OVERRIDE_PATH <<EOF
|
|
||||||
import Config
|
|
||||||
|
|
||||||
config :pleroma, Pleroma.Captcha,
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
config :pleroma, :instance,
|
|
||||||
registrations_open: true,
|
|
||||||
account_activation_required: false,
|
|
||||||
approval_required: false
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo '-- Running migrations...'
|
|
||||||
/opt/pleroma/bin/pleroma_ctl migrate
|
|
||||||
|
|
||||||
echo '-- Starting!'
|
|
||||||
/opt/pleroma/bin/pleroma start &
|
|
||||||
PLEROMA_PID=$!
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if kill -0 $PLEROMA_PID 2>/dev/null; then
|
|
||||||
kill -TERM $PLEROMA_PID
|
|
||||||
wait $PLEROMA_PID || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup INT TERM
|
|
||||||
|
|
||||||
echo '-- Waiting for API...'
|
|
||||||
api_ok=false
|
|
||||||
for _i in $(seq 1 120); do
|
|
||||||
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
|
|
||||||
api_ok=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1s
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $api_ok != true ]; then
|
|
||||||
echo 'Timed out waiting for Pleroma API to become available'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f $SEED_SENTINEL_PATH ]; then
|
|
||||||
if [ -n ${E2E_ADMIN_USERNAME:-} ] && [ -n ${E2E_ADMIN_PASSWORD:-} ] && [ -n ${E2E_ADMIN_EMAIL:-} ]; then
|
|
||||||
echo '-- Seeding admin user' $E2E_ADMIN_USERNAME '...'
|
|
||||||
if ! /opt/pleroma/bin/pleroma_ctl user new $E2E_ADMIN_USERNAME $E2E_ADMIN_EMAIL --admin --password $E2E_ADMIN_PASSWORD -y; then
|
|
||||||
echo '-- User already exists or creation failed, ensuring admin + confirmed...'
|
|
||||||
/opt/pleroma/bin/pleroma_ctl user set $E2E_ADMIN_USERNAME --admin --confirmed
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo '-- Skipping admin seeding (missing E2E_ADMIN_* env)'
|
|
||||||
fi
|
|
||||||
|
|
||||||
touch $SEED_SENTINEL_PATH
|
|
||||||
fi
|
|
||||||
|
|
||||||
wait $PLEROMA_PID
|
|
||||||
tags:
|
|
||||||
- amd64
|
|
||||||
- himem
|
|
||||||
variables:
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
|
||||||
FF_NETWORK_PER_BUILD: "true"
|
|
||||||
PLEROMA_IMAGE: git.pleroma.social/pleroma/pleroma:stable
|
|
||||||
POSTGRES_USER: pleroma
|
|
||||||
POSTGRES_PASSWORD: pleroma
|
|
||||||
POSTGRES_DB: pleroma
|
|
||||||
DB_USER: pleroma
|
|
||||||
DB_PASS: pleroma
|
|
||||||
DB_NAME: pleroma
|
|
||||||
DB_HOST: db
|
|
||||||
DB_PORT: 5432
|
|
||||||
DOMAIN: localhost
|
|
||||||
INSTANCE_NAME: Pleroma E2E
|
|
||||||
E2E_ADMIN_USERNAME: admin
|
|
||||||
E2E_ADMIN_PASSWORD: adminadmin
|
|
||||||
E2E_ADMIN_EMAIL: admin@example.com
|
|
||||||
ADMIN_EMAIL: $E2E_ADMIN_EMAIL
|
|
||||||
NOTIFY_EMAIL: $E2E_ADMIN_EMAIL
|
|
||||||
VITE_PROXY_TARGET: http://pleroma:4000
|
|
||||||
VITE_PROXY_ORIGIN: http://localhost:4000
|
|
||||||
E2E_BASE_URL: http://localhost:8099
|
|
||||||
script:
|
|
||||||
- npm install -g yarn@1.22.22
|
|
||||||
- yarn --frozen-lockfile
|
|
||||||
- |
|
|
||||||
echo "-- Waiting for Pleroma API..."
|
|
||||||
api_ok="false"
|
|
||||||
for _i in $(seq 1 120); do
|
|
||||||
if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then
|
|
||||||
api_ok="true"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1s
|
|
||||||
done
|
|
||||||
if [ "$api_ok" != "true" ]; then
|
|
||||||
echo "Timed out waiting for Pleroma API to become available"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- yarn e2e:pw
|
|
||||||
artifacts:
|
|
||||||
when: on_failure
|
|
||||||
paths:
|
|
||||||
- test/e2e-playwright/test-results
|
|
||||||
- test/e2e-playwright/playwright-report
|
|
||||||
|
|
||||||
build:
|
|
||||||
stage: build
|
|
||||||
tags:
|
|
||||||
- amd64
|
|
||||||
- himem
|
|
||||||
script:
|
|
||||||
- yarn
|
|
||||||
- yarn build
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- dist/
|
|
||||||
|
|
||||||
docs-deploy:
|
|
||||||
stage: deploy
|
|
||||||
image: alpine:latest
|
|
||||||
only:
|
|
||||||
- develop@pleroma/pleroma-fe
|
|
||||||
before_script:
|
|
||||||
- apk add curl
|
|
||||||
script:
|
|
||||||
- curl -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' https://git.pleroma.social/api/v4/projects/673/trigger/pipeline
|
|
||||||
|
|
@ -14,7 +14,7 @@ labels:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
build:
|
build:
|
||||||
image: docker.io/node:20-alpine
|
image: docker.io/node:24-alpine
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache zip git
|
- apk add --no-cache zip git
|
||||||
- yarn --frozen-lockfile
|
- yarn --frozen-lockfile
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ labels:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
build:
|
build:
|
||||||
image: docker.io/node:20-alpine
|
image: docker.io/node:24-alpine
|
||||||
commands:
|
commands:
|
||||||
- yarn --frozen-lockfile
|
- yarn --frozen-lockfile
|
||||||
- yarn build
|
- yarn build
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,10 @@ when:
|
||||||
steps:
|
steps:
|
||||||
install-depends:
|
install-depends:
|
||||||
image: &node-image
|
image: &node-image
|
||||||
docker.io/node:20-alpine
|
docker.io/node:24-alpine
|
||||||
commands:
|
commands:
|
||||||
- yarn --frozen-lockfile
|
- yarn --frozen-lockfile
|
||||||
|
|
||||||
eslint:
|
|
||||||
image: *node-image
|
|
||||||
depends_on: install-depends
|
|
||||||
commands:
|
|
||||||
- yarn ci-eslint
|
|
||||||
|
|
||||||
biome:
|
biome:
|
||||||
image: *node-image
|
image: *node-image
|
||||||
depends_on: install-depends
|
depends_on: install-depends
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ steps:
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||||
entrypoint: *script_file_entrypoint
|
entrypoint: *script_file_entrypoint
|
||||||
commands:
|
commands:
|
||||||
|
- npm install --global yarn
|
||||||
- |
|
- |
|
||||||
if [ "${CI_PIPELINE_EVENT}" != "pull_request" ]; then
|
if [ "${CI_PIPELINE_EVENT}" != "pull_request" ]; then
|
||||||
mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.5.11/schema.json",
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"vue": "recommended"
|
"vue": "recommended"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": false,
|
"preset": "none",
|
||||||
"complexity": {
|
"complexity": {
|
||||||
"noAdjacentSpacesInRegex": "error",
|
"noAdjacentSpacesInRegex": "error",
|
||||||
"noExtraBooleanCast": "error",
|
"noExtraBooleanCast": "error",
|
||||||
|
|
@ -80,6 +80,9 @@
|
||||||
"noUselessRegexBackrefs": "error",
|
"noUselessRegexBackrefs": "error",
|
||||||
"noWith": "error",
|
"noWith": "error",
|
||||||
"useGetterReturn": "error"
|
"useGetterReturn": "error"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"useVueMultiWordComponentNames": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { readFile } from 'node:fs/promises'
|
|
||||||
import { resolve } from 'node:path'
|
|
||||||
|
|
||||||
const target = 'node_modules/msw/lib/mockServiceWorker.js'
|
|
||||||
|
|
||||||
const mswPlugin = () => {
|
|
||||||
let projectRoot
|
|
||||||
return {
|
|
||||||
name: 'msw-plugin',
|
|
||||||
apply: 'serve',
|
|
||||||
configResolved(conf) {
|
|
||||||
projectRoot = conf.root
|
|
||||||
},
|
|
||||||
configureServer(server) {
|
|
||||||
server.middlewares.use(async (req, res, next) => {
|
|
||||||
if (req.path === '/mockServiceWorker.js') {
|
|
||||||
const file = await readFile(resolve(projectRoot, target))
|
|
||||||
res.set('Content-Type', 'text/javascript')
|
|
||||||
res.send(file)
|
|
||||||
} else {
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default mswPlugin
|
|
||||||
1
changelog.d/node24.change
Normal file
1
changelog.d/node24.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Building PleromaFE now requires NodeJS version 24 or newer
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import js from '@eslint/js'
|
|
||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
||||||
import vue from 'eslint-plugin-vue'
|
|
||||||
import globals from 'globals'
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
...vue.configs['flat/recommended'],
|
|
||||||
globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']),
|
|
||||||
{
|
|
||||||
files: ['src/**/*.vue'],
|
|
||||||
plugins: { js },
|
|
||||||
extends: ['js/recommended'],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: 2024,
|
|
||||||
sourceType: 'module',
|
|
||||||
|
|
||||||
parserOptions: {
|
|
||||||
parser: '@babel/eslint-parser',
|
|
||||||
},
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
...globals.vitest,
|
|
||||||
...globals.chai,
|
|
||||||
...globals.commonjs,
|
|
||||||
...globals.serviceworker,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
rules: {
|
|
||||||
'vue/require-prop-types': 0,
|
|
||||||
'vue/multi-word-component-names': 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
132
package.json
132
package.json
|
|
@ -15,116 +15,80 @@
|
||||||
"e2e": "sh ./tools/e2e/run.sh",
|
"e2e": "sh ./tools/e2e/run.sh",
|
||||||
"test": "yarn run unit && yarn run e2e",
|
"test": "yarn run unit && yarn run e2e",
|
||||||
"ci-biome": "yarn exec biome check",
|
"ci-biome": "yarn exec biome check",
|
||||||
"ci-eslint": "yarn exec eslint",
|
|
||||||
"ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
|
"ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
|
||||||
"lint": "yarn ci-biome; yarn ci-eslint; yarn ci-stylelint",
|
"lint": "yarn ci-biome; yarn ci-stylelint",
|
||||||
"lint-fix": "yarn exec eslint -- --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
|
"lint-fix": "yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.28.4",
|
|
||||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "7.1.0",
|
"@fortawesome/fontawesome-svg-core": "7.3.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "7.1.0",
|
"@fortawesome/free-regular-svg-icons": "7.3.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "7.1.0",
|
"@fortawesome/free-solid-svg-icons": "7.3.1",
|
||||||
"@fortawesome/vue-fontawesome": "3.1.2",
|
"@fortawesome/vue-fontawesome": "3.3.3",
|
||||||
"@kazvmoe-infra/pinch-zoom-element": "1.3.0",
|
"@kazvmoe-infra/pinch-zoom-element": "1.3.0",
|
||||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
||||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.6.22",
|
"@ruffle-rs/ruffle": "0.6.0-nightly.2026.9.4",
|
||||||
"@vuelidate/core": "2.0.3",
|
"@vuelidate/core": "2.0.3",
|
||||||
"@vuelidate/validators": "2.0.4",
|
"@vuelidate/validators": "2.0.4",
|
||||||
"@web3-storage/parse-link-header": "^3.1.0",
|
"@web3-storage/parse-link-header": "3.1.0",
|
||||||
"body-scroll-lock": "3.1.5",
|
"body-scroll-lock": "3.1.5",
|
||||||
"chromatism": "3.0.0",
|
"chromatism": "3.0.0",
|
||||||
"click-outside-vue3": "4.0.1",
|
"click-outside-vue3": "4.0.1",
|
||||||
"cropperjs": "2.0.1",
|
"cropperjs": "2.2.0",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"globals": "^16.0.0",
|
"hash-sum": "2.0.0",
|
||||||
"hash-sum": "^2.0.0",
|
"js-cookie": "3.0.8",
|
||||||
"js-cookie": "3.0.5",
|
|
||||||
"localforage": "1.10.0",
|
"localforage": "1.10.0",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"parse-link-header": "2.0.0",
|
|
||||||
"phoenix": "1.8.1",
|
"phoenix": "1.8.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "4.0.3",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"querystring-es3": "0.2.1",
|
|
||||||
"url": "0.11.4",
|
|
||||||
"utf8": "3.0.0",
|
"utf8": "3.0.0",
|
||||||
"uuid": "11.1.0",
|
"uuid": "14.0.2",
|
||||||
"vue": "3.5.22",
|
"vue": "3.5.42",
|
||||||
"vue-i18n": "11",
|
"vue-i18n": "11.4.10",
|
||||||
"vue-router": "4.6.4",
|
"vue-router": "5.3.1",
|
||||||
"vue-virtual-scroller": "^2.0.0-beta.7"
|
"vue-virtual-scroller": "3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.28.5",
|
"@biomejs/biome": "2.5.11",
|
||||||
"@babel/eslint-parser": "7.28.5",
|
"@pinia/testing": "2.0.1",
|
||||||
"@babel/plugin-transform-runtime": "7.28.5",
|
|
||||||
"@babel/preset-env": "7.28.5",
|
|
||||||
"@babel/register": "7.28.3",
|
|
||||||
"@biomejs/biome": "2.3.11",
|
|
||||||
"@pinia/testing": "1.0.3",
|
|
||||||
"@ungap/event-target": "0.2.4",
|
"@ungap/event-target": "0.2.4",
|
||||||
"@vitejs/devtools": "^0.3.1",
|
"@vitejs/devtools": "0.7.2",
|
||||||
"@vitejs/plugin-vue": "^6.0.7",
|
"@vitejs/plugin-vue": "6.0.8",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
"@vitejs/plugin-vue-jsx": "5.1.6",
|
||||||
"@vitest/browser": "^4.1.7",
|
"@vitest/browser": "5.0.0",
|
||||||
"@vitest/browser-playwright": "^4.1.7",
|
"@vitest/browser-playwright": "5.0.0",
|
||||||
"@vitest/coverage-v8": "^4.1.10",
|
"@vitest/coverage-v8": "5.0.0",
|
||||||
"@vitest/ui": "^4.1.7",
|
"@vitest/ui": "5.0.0",
|
||||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
"@vue/compiler-sfc": "3.5.42",
|
||||||
"@vue/babel-plugin-jsx": "1.5.0",
|
"@vue/compiler-dom": "3.5.42",
|
||||||
"@vue/compiler-sfc": "3.5.22",
|
"@vue/devtools-api": "8.2.0",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.5.0",
|
||||||
"autoprefixer": "10.4.21",
|
"autoprefixer": "10.5.4",
|
||||||
"chai": "5.3.3",
|
"iso-639-1": "3.1.6",
|
||||||
"chalk": "5.6.2",
|
|
||||||
"chromedriver": "135.0.4",
|
|
||||||
"connect-history-api-fallback": "2.0.0",
|
|
||||||
"cross-spawn": "7.0.6",
|
|
||||||
"custom-event-polyfill": "1.0.7",
|
|
||||||
"eslint": "9.39.2",
|
|
||||||
"eslint-config-standard": "17.1.0",
|
|
||||||
"eslint-formatter-friendly": "7.0.0",
|
|
||||||
"eslint-plugin-import": "2.32.0",
|
|
||||||
"eslint-plugin-n": "17.23.1",
|
|
||||||
"eslint-plugin-promise": "7.2.1",
|
|
||||||
"eslint-plugin-vue": "10.6.2",
|
|
||||||
"eventsource-polyfill": "0.9.6",
|
|
||||||
"express": "5.1.0",
|
|
||||||
"function-bind": "1.1.2",
|
|
||||||
"http-proxy-middleware": "3.0.5",
|
|
||||||
"iso-639-1": "3.1.5",
|
|
||||||
"msw": "2.14.6",
|
|
||||||
"nightwatch": "3.12.2",
|
|
||||||
"oxc": "^1.0.1",
|
|
||||||
"playwright": "1.61.0",
|
"playwright": "1.61.0",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.28",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "2.0.0",
|
||||||
"postcss-scss": "^4.0.6",
|
"postcss-scss": "4.0.9",
|
||||||
"sass-embedded": "^1.100.0",
|
"sass-embedded": "1.104.0",
|
||||||
"selenium-server": "3.141.59",
|
|
||||||
"semver": "7.7.3",
|
|
||||||
"serve-static": "2.2.0",
|
"serve-static": "2.2.0",
|
||||||
"shelljs": "0.10.0",
|
"stylelint": "17.14.0",
|
||||||
"sinon": "20.0.0",
|
"stylelint-config-html": "2.0.0",
|
||||||
"sinon-chai": "4.0.1",
|
"stylelint-config-recommended": "18.0.0",
|
||||||
"stylelint": "16.25.0",
|
"stylelint-config-recommended-scss": "17.0.1",
|
||||||
"stylelint-config-html": "^1.1.0",
|
"stylelint-config-recommended-vue": "2.0.0",
|
||||||
"stylelint-config-recommended": "^16.0.0",
|
"stylelint-config-standard": "40.0.0",
|
||||||
"stylelint-config-recommended-scss": "^14.0.0",
|
"vite": "8.2.2",
|
||||||
"stylelint-config-recommended-vue": "^1.6.0",
|
"vite-plugin-biome": "^1.2.0",
|
||||||
"stylelint-config-standard": "38.0.0",
|
"vite-plugin-stylelint": "6.3.0",
|
||||||
"vite": "^8.0.0",
|
"vite-plugin-vue-devtools": "8.2.1",
|
||||||
"vite-plugin-eslint2": "^5.1.0",
|
"vitest": "5.0.0"
|
||||||
"vite-plugin-stylelint": "^6.1.0",
|
|
||||||
"vitest": "^4.1.7",
|
|
||||||
"vue-eslint-parser": "10.2.0"
|
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16.0.0"
|
"node": ">= 24.0.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -664,7 +664,7 @@ option {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: row dense;
|
grid-auto-flow: row dense;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
@ -923,7 +923,9 @@ option {
|
||||||
margin: -1px;
|
margin: -1px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
/* stylelint-disable property-no-deprecated */
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
|
/* stylelint-enable property-no-deprecated */
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"x-slide x-slide x-slide . "
|
"x-slide x-slide x-slide . "
|
||||||
"x-num x-num y-num y-num "
|
"x-num x-num y-num y-num "
|
||||||
"assists assists assists assists";
|
"assists assists assists assists";
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
&:not(.-shadow-controls) {
|
&:not(.-shadow-controls) {
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
grid-auto-rows: 2em;
|
grid-auto-rows: 2em;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-light-grid {
|
.input-light-grid {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
grid-template-columns: min-content;
|
grid-template-columns: min-content;
|
||||||
grid-auto-columns: min-content;
|
grid-auto-columns: min-content;
|
||||||
grid-auto-flow: column dense;
|
grid-auto-flow: column dense;
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-auto-columns: 10em;
|
grid-auto-columns: 10em;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@
|
||||||
height: auto;
|
height: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
|
||||||
|
|
||||||
.gallery-item {
|
.gallery-item {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
|
|
||||||
&-buttons-wrapper {
|
&-buttons-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-auto-columns: var(--__line-height);
|
grid-auto-columns: var(--__line-height);
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-gap: var(--__horizontal-gap);
|
gap: var(--__horizontal-gap);
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
&[aria-expanded] {
|
&[aria-expanded] {
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ const updatePalette = (paletteKey, value) => {
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
grid-template-rows: repeat(5, 1fr) auto;
|
grid-template-rows: repeat(5, 1fr) auto;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
|
|
@ -196,7 +196,7 @@ const updatePalette = (paletteKey, value) => {
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
grid-auto-rows: auto;
|
grid-auto-rows: auto;
|
||||||
grid-template-columns: repeat(auto-fill, 10em);
|
grid-template-columns: repeat(auto-fill, 10em);
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-auto-columns: auto;
|
grid-auto-columns: auto;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
.popover-wrapper {
|
.popover-wrapper {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
.main-button {
|
.main-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--__horizontal-gap) var(--__horizontal-gap);
|
padding: var(--__horizontal-gap) var(--__horizontal-gap);
|
||||||
grid-gap: var(--__horizontal-gap);
|
gap: var(--__horizontal-gap);
|
||||||
grid-template-columns: 1fr var(--__line-height);
|
grid-template-columns: 1fr var(--__line-height);
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-auto-columns: auto;
|
grid-auto-columns: auto;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export default {
|
||||||
...Setting.methods,
|
...Setting.methods,
|
||||||
getValue(e) {
|
getValue(e) {
|
||||||
// Basic tri-state toggle implementation
|
// Basic tri-state toggle implementation
|
||||||
if (!!this.indeterminateState && !e && this.visibleState === true) {
|
if (this.indeterminateState && !e && this.visibleState === true) {
|
||||||
// If we have indeterminate state, switching from true to false first goes through indeterminate
|
// If we have indeterminate state, switching from true to false first goes through indeterminate
|
||||||
return this.indeterminateState
|
return this.indeterminateState
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ div.PWAManifestIconsSetting {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, 12em);
|
grid-template-columns: repeat(auto-fit, 12em);
|
||||||
grid-gap: 2em;
|
gap: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
dl {
|
dl {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// eslint-disable-next-line no-unused
|
|
||||||
|
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { Fragment } from 'vue'
|
import { Fragment } from 'vue'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@
|
||||||
.palettes {
|
.palettes {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.never {
|
.never {
|
||||||
|
|
@ -69,7 +69,7 @@
|
||||||
grid-column: 1 / span 3;
|
grid-column: 1 / span 3;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
|
|
||||||
.total {
|
.total {
|
||||||
|
|
|
||||||
|
|
@ -846,7 +846,7 @@ export default {
|
||||||
exports.previewClass = computed(() => {
|
exports.previewClass = computed(() => {
|
||||||
const selectors = []
|
const selectors = []
|
||||||
if (
|
if (
|
||||||
!!selectedComponent.value.variants?.normal ||
|
selectedComponent.value.variants?.normal ||
|
||||||
selectedVariant.value !== 'normal'
|
selectedVariant.value !== 'normal'
|
||||||
) {
|
) {
|
||||||
selectors.push(selectedComponent.value.variants[selectedVariant.value])
|
selectors.push(selectedComponent.value.variants[selectedVariant.value])
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template:
|
grid-template:
|
||||||
"meta preview";
|
"meta preview";
|
||||||
grid-gap: 1em;
|
gap: 1em;
|
||||||
grid-template-columns: min-content 6fr;
|
grid-template-columns: min-content 6fr;
|
||||||
|
|
||||||
.theme-preview-container {
|
.theme-preview-container {
|
||||||
|
|
@ -110,7 +110,7 @@
|
||||||
"movement editor";
|
"movement editor";
|
||||||
grid-template-columns: 10em 1fr;
|
grid-template-columns: 10em 1fr;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
.list-edit-area {
|
.list-edit-area {
|
||||||
grid-area: editor;
|
grid-area: editor;
|
||||||
|
|
@ -141,7 +141,7 @@
|
||||||
grid-template-columns: auto 1fr auto 10em;
|
grid-template-columns: auto 1fr auto 10em;
|
||||||
grid-template-rows: subgrid;
|
grid-template-rows: subgrid;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
grid-gap: 0 0.5em;
|
gap: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-edit-area {
|
.list-edit-area {
|
||||||
|
|
@ -158,7 +158,7 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 6fr 3fr 4fr;
|
grid-template-columns: 6fr 3fr 4fr;
|
||||||
grid-template-rows: auto auto 1fr;
|
grid-template-rows: auto auto 1fr;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"component component variant"
|
"component component variant"
|
||||||
"state state state"
|
"state state state"
|
||||||
|
|
@ -176,7 +176,7 @@
|
||||||
grid-template-columns: 1fr minmax(10em, 1fr);
|
grid-template-columns: 1fr minmax(10em, 1fr);
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
> label:not(.Select) {
|
> label:not(.Select) {
|
||||||
|
|
@ -200,7 +200,7 @@
|
||||||
grid-auto-flow: dense;
|
grid-auto-flow: dense;
|
||||||
grid-template-columns: repeat(5, minmax(min-content, 1fr));
|
grid-template-columns: repeat(5, minmax(min-content, 1fr));
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +220,7 @@
|
||||||
.editor-tab {
|
.editor-tab {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 2em;
|
grid-template-columns: 1fr 2em;
|
||||||
grid-column-gap: 0.5em;
|
column-gap: 0.5em;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-auto-rows: min-content;
|
grid-auto-rows: min-content;
|
||||||
grid-auto-flow: dense;
|
grid-auto-flow: dense;
|
||||||
|
|
@ -272,7 +272,7 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +286,7 @@
|
||||||
.style-actions {
|
.style-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(7em, 1fr));
|
grid-template-columns: repeat(4, minmax(7em, 1fr));
|
||||||
grid-gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
grid-template-columns: 10em 1fr 1fr;
|
grid-template-columns: 10em 1fr 1fr;
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
grid-template-areas: "selector preview tweak";
|
grid-template-areas: "selector preview tweak";
|
||||||
grid-gap: 0.5em;
|
gap: 0.5em;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
|
|
||||||
.style-control {
|
.style-control {
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
margin-right: 0.125em;
|
margin-right: 0.125em;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
grid-gap: 0.25em;
|
gap: 0.25em;
|
||||||
|
|
||||||
/* hack */
|
/* hack */
|
||||||
.input-boolean {
|
.input-boolean {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
.action-button-inner {
|
.action-button-inner {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 0.125em;
|
gap: 0.125em;
|
||||||
grid-template-columns: max-content;
|
grid-template-columns: max-content;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-auto-columns: max-content;
|
grid-auto-columns: max-content;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
grid-template-columns: repeat(auto-fill, minmax(3.75em, 10%));
|
grid-template-columns: repeat(auto-fill, minmax(3.75em, 10%));
|
||||||
grid-auto-flow: row dense;
|
grid-auto-flow: row dense;
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-gap: 0.5em 0.1em;
|
gap: 0.5em 0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pin-action-button {
|
.pin-action-button {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// eslint-disable-next-line no-unused
|
|
||||||
|
|
||||||
import { Fragment } from 'vue'
|
import { Fragment } from 'vue'
|
||||||
|
|
||||||
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
grid-auto-columns: auto;
|
grid-auto-columns: auto;
|
||||||
grid-column-gap: var(--__panel-heading-gap);
|
column-gap: var(--__panel-heading-gap);
|
||||||
padding: var(--panel-heading-height-padding);
|
padding: var(--panel-heading-height-padding);
|
||||||
height: var(--__panel-heading-height);
|
height: var(--__panel-heading-height);
|
||||||
line-height: var(--__panel-heading-height-inner);
|
line-height: var(--__panel-heading-height-inner);
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-gap: 0.6em;
|
gap: 0.6em;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
align-self: end;
|
align-self: end;
|
||||||
|
|
@ -255,7 +255,6 @@
|
||||||
&.-overlay {
|
&.-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -0.6em;
|
inset: -0.6em;
|
||||||
left: -0.6em;
|
|
||||||
right: -1.2em;
|
right: -1.2em;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -419,7 +418,7 @@
|
||||||
.user-stats {
|
.user-stats {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
word-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
dl {
|
dl {
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ const ensureFinalFallback = (codes) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
languages,
|
|
||||||
langCodeToJsonName,
|
|
||||||
langCodeToCldrName,
|
|
||||||
ensureFinalFallback,
|
ensureFinalFallback,
|
||||||
|
langCodeToCldrName,
|
||||||
|
langCodeToJsonName,
|
||||||
|
languages,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import EventTargetPolyfill from '@ungap/event-target'
|
import EventTargetPolyfill from '@ungap/event-target'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// for some reason eslint both likes and dislikes
|
|
||||||
// no-new here so we just call something useless
|
|
||||||
// so it stops reporting this file
|
|
||||||
const et = new EventTarget()
|
const et = new EventTarget()
|
||||||
et.dispatchEvent()
|
et.dispatchEvent()
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
import 'custom-event-polyfill'
|
|
||||||
import './lib/event_target_polyfill.js'
|
import './lib/event_target_polyfill.js'
|
||||||
|
|
||||||
// Polyfill for Array.prototype.toSorted (ES2023)
|
// Polyfill for Array.prototype.toSorted (ES2023)
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-template-columns: minmax(50%, 1fr);
|
grid-template-columns: minmax(50%, 1fr);
|
||||||
grid-auto-columns: auto;
|
grid-auto-columns: auto;
|
||||||
grid-column-gap: var(--__panel-heading-gap);
|
column-gap: var(--__panel-heading-gap);
|
||||||
flex: none;
|
flex: none;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
padding: var(--panel-heading-height-padding);
|
padding: var(--panel-heading-height-padding);
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ export class RegistrationError extends Error {
|
||||||
// the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors
|
// the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors
|
||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
error = JSON.parse(error)
|
error = JSON.parse(error)
|
||||||
// eslint-disable-next-line
|
|
||||||
if (Object.hasOwn(error, 'error')) {
|
if (Object.hasOwn(error, 'error')) {
|
||||||
error = JSON.parse(error.error)
|
error = JSON.parse(error.error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ export const combinationsMatch = (criteria, subject, strict) => {
|
||||||
*/
|
*/
|
||||||
export const findRules = (criteria, strict) => (subject) => {
|
export const findRules = (criteria, strict) => (subject) => {
|
||||||
// If we searching for "general" rules - ignore "specific" ones
|
// If we searching for "general" rules - ignore "specific" ones
|
||||||
if (criteria.parent === null && !!subject.parent) return false
|
if (criteria.parent === null && subject.parent) return false
|
||||||
if (!combinationsMatch(criteria, subject, strict)) return false
|
if (!combinationsMatch(criteria, subject, strict)) return false
|
||||||
|
|
||||||
if (criteria.parent !== undefined && criteria.parent !== null) {
|
if (criteria.parent !== undefined && criteria.parent !== null) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
/* eslint-env serviceworker */
|
|
||||||
|
|
||||||
// biome-ignore: side effect import of assets list
|
// biome-ignore: side effect import of assets list
|
||||||
import 'virtual:pleroma-fe/service_worker_env'
|
import 'virtual:pleroma-fe/service_worker_env'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
// A custom Nightwatch assertion.
|
|
||||||
// the name of the method is the filename.
|
|
||||||
// can be used in tests like this:
|
|
||||||
//
|
|
||||||
// browser.assert.elementCount(selector, count)
|
|
||||||
//
|
|
||||||
// for how to write custom assertions see
|
|
||||||
// http://nightwatchjs.org/guide#writing-custom-assertions
|
|
||||||
exports.assertion = function (selector, count) {
|
|
||||||
this.message = 'Testing if element <' + selector + '> has count: ' + count
|
|
||||||
this.expected = count
|
|
||||||
this.pass = function (val) {
|
|
||||||
return val === this.expected
|
|
||||||
}
|
|
||||||
this.value = function (res) {
|
|
||||||
return res.value
|
|
||||||
}
|
|
||||||
this.command = function (cb) {
|
|
||||||
const self = this
|
|
||||||
return this.api.execute(
|
|
||||||
function (selector) {
|
|
||||||
return document.querySelectorAll(selector).length
|
|
||||||
},
|
|
||||||
[selector],
|
|
||||||
function (res) {
|
|
||||||
cb.call(self, res)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
require('@babel/register')
|
|
||||||
const config = require('../../config')
|
|
||||||
|
|
||||||
// http://nightwatchjs.org/guide#settings-file
|
|
||||||
module.exports = {
|
|
||||||
src_folders: ['test/e2e/specs'],
|
|
||||||
output_folder: 'test/e2e/reports',
|
|
||||||
custom_assertions_path: ['test/e2e/custom-assertions'],
|
|
||||||
|
|
||||||
selenium: {
|
|
||||||
start_process: true,
|
|
||||||
server_path: require('selenium-server').path,
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 4444,
|
|
||||||
cli_args: {
|
|
||||||
'webdriver.chrome.driver': require('chromedriver').path,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
test_settings: {
|
|
||||||
default: {
|
|
||||||
selenium_port: 4444,
|
|
||||||
selenium_host: 'localhost',
|
|
||||||
silent: true,
|
|
||||||
globals: {
|
|
||||||
devServerURL:
|
|
||||||
'http://localhost:' + (process.env.PORT || config.dev.port),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
chrome: {
|
|
||||||
desiredCapabilities: {
|
|
||||||
browserName: 'chrome',
|
|
||||||
javascriptEnabled: true,
|
|
||||||
acceptSslCerts: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
firefox: {
|
|
||||||
desiredCapabilities: {
|
|
||||||
browserName: 'firefox',
|
|
||||||
javascriptEnabled: true,
|
|
||||||
acceptSslCerts: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// 1. start the dev server using production config
|
|
||||||
process.env.NODE_ENV = 'testing'
|
|
||||||
const server = require('../../build/dev-server.js')
|
|
||||||
|
|
||||||
// 2. run the nightwatch test suite against it
|
|
||||||
// to run in additional browsers:
|
|
||||||
// 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
|
|
||||||
// 2. add it to the --env flag below
|
|
||||||
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
|
|
||||||
// For more information on Nightwatch's config file, see
|
|
||||||
// http://nightwatchjs.org/guide#settings-file
|
|
||||||
let opts = process.argv.slice(2)
|
|
||||||
if (!opts.includes('--config')) {
|
|
||||||
opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
|
|
||||||
}
|
|
||||||
if (!opts.includes('--env')) {
|
|
||||||
opts = opts.concat(['--env', 'chrome'])
|
|
||||||
}
|
|
||||||
|
|
||||||
const spawn = require('cross-spawn')
|
|
||||||
const runner = spawn('./node_modules/.bin/nightwatch', opts, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
})
|
|
||||||
|
|
||||||
runner.on('exit', function (code) {
|
|
||||||
server.close()
|
|
||||||
process.exit(code)
|
|
||||||
})
|
|
||||||
|
|
||||||
runner.on('error', function (err) {
|
|
||||||
server.close()
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
// For authoring Nightwatch tests, see
|
|
||||||
// http://nightwatchjs.org/guide#usage
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
'default e2e tests': function (browser) {
|
|
||||||
// automatically uses dev Server port from /config.index.js
|
|
||||||
// default: http://localhost:8099
|
|
||||||
// see nightwatch.conf.js
|
|
||||||
const devServer = browser.globals.devServerURL
|
|
||||||
|
|
||||||
browser
|
|
||||||
.url(devServer)
|
|
||||||
.waitForElementVisible('#app', 5000)
|
|
||||||
.assert.elementPresent('.hello')
|
|
||||||
.assert.containsText('h1', 'Welcome to Your Vue.js App')
|
|
||||||
.assert.elementCount('img', 1)
|
|
||||||
.end()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
19
test/fixtures/mock_api.js
vendored
19
test/fixtures/mock_api.js
vendored
|
|
@ -1,19 +0,0 @@
|
||||||
import { test as testBase } from 'vitest'
|
|
||||||
|
|
||||||
import { worker } from './worker.js'
|
|
||||||
|
|
||||||
export const test = testBase.extend({
|
|
||||||
worker: [
|
|
||||||
// biome-ignore lint: required by vitest
|
|
||||||
async ({}, use) => {
|
|
||||||
await worker.start()
|
|
||||||
|
|
||||||
await use(worker)
|
|
||||||
|
|
||||||
worker.resetHandlers()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
auto: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
5
test/fixtures/worker.js
vendored
5
test/fixtures/worker.js
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
import { setupWorker } from 'msw/browser'
|
|
||||||
|
|
||||||
export const worker = setupWorker()
|
|
||||||
|
|
||||||
window.__test__ = window.__test__ || 'TEST'
|
|
||||||
|
|
@ -1,23 +1,18 @@
|
||||||
import { createTestingPinia } from '@pinia/testing'
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
import { HttpResponse, http } from 'msw'
|
|
||||||
import { setActivePinia } from 'pinia'
|
import { setActivePinia } from 'pinia'
|
||||||
|
|
||||||
import { test as it } from '/test/fixtures/mock_api.js'
|
|
||||||
|
|
||||||
import { useListsStore } from 'src/stores/lists.js'
|
import { useListsStore } from 'src/stores/lists.js'
|
||||||
|
|
||||||
import { MASTODON_LIST_ACCOUNTS_URL, MASTODON_LIST_URL } from 'src/api/user.js'
|
import { MASTODON_LIST_ACCOUNTS_URL, MASTODON_LIST_URL } from 'src/api/user.js'
|
||||||
|
|
||||||
describe('The lists store', () => {
|
describe('The lists store', () => {
|
||||||
let store
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||||
store = useListsStore()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('actions', () => {
|
describe('actions', () => {
|
||||||
it('updates array of all lists', () => {
|
it('updates array of all lists', () => {
|
||||||
|
const store = useListsStore()
|
||||||
const list = { id: '1', title: 'testList' }
|
const list = { id: '1', title: 'testList' }
|
||||||
|
|
||||||
store.setLists([list])
|
store.setLists([list])
|
||||||
|
|
@ -25,19 +20,33 @@ describe('The lists store', () => {
|
||||||
expect(store.allLists).to.eql([list])
|
expect(store.allLists).to.eql([list])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a new list with a title, updating the title for existing lists', async ({
|
it('adds a new list with a title, updating the title for existing lists', async () => {
|
||||||
worker,
|
const store = useListsStore()
|
||||||
}) => {
|
|
||||||
const list = { id: '1', title: 'testList' }
|
const list = { id: '1', title: 'testList' }
|
||||||
const modList = { id: '1', title: 'anotherTestTitle' }
|
const modList = { id: '1', title: 'anotherTestTitle' }
|
||||||
|
|
||||||
worker.use(
|
const mockFetch = vi
|
||||||
http.put(MASTODON_LIST_URL(':id'), () =>
|
.fn()
|
||||||
HttpResponse.json({ ok: true }),
|
.mockResolvedValueOnce(
|
||||||
),
|
new Response(JSON.stringify({ ok: true }), {
|
||||||
)
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify({ ok: true }), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
await store.setList({ listId: list.id, title: list.title })
|
await store.setList({ listId: list.id, title: list.title })
|
||||||
|
|
||||||
|
expect(mockFetch).to.have.been.calledOnce
|
||||||
|
expect(mockFetch.mock.calls[0][0]).to.eql(MASTODON_LIST_URL('1'))
|
||||||
|
expect(mockFetch.mock.calls[0][1]).to.have.property('method', 'PUT')
|
||||||
|
mockFetch.mockClear()
|
||||||
|
|
||||||
expect(store.allListsObject[list.id]).to.eql({
|
expect(store.allListsObject[list.id]).to.eql({
|
||||||
title: list.title,
|
title: list.title,
|
||||||
accountIds: [],
|
accountIds: [],
|
||||||
|
|
@ -46,6 +55,11 @@ describe('The lists store', () => {
|
||||||
expect(store.allLists[0]).to.eql(list)
|
expect(store.allLists[0]).to.eql(list)
|
||||||
|
|
||||||
await store.setList({ listId: modList.id, title: modList.title })
|
await store.setList({ listId: modList.id, title: modList.title })
|
||||||
|
|
||||||
|
expect(mockFetch).to.have.been.calledOnce
|
||||||
|
expect(mockFetch.mock.calls[0][0]).to.eql(MASTODON_LIST_URL('1'))
|
||||||
|
expect(mockFetch.mock.calls[0][1]).to.have.property('method', 'PUT')
|
||||||
|
|
||||||
expect(store.allListsObject[modList.id]).to.eql({
|
expect(store.allListsObject[modList.id]).to.eql({
|
||||||
title: modList.title,
|
title: modList.title,
|
||||||
accountIds: [],
|
accountIds: [],
|
||||||
|
|
@ -54,25 +68,39 @@ describe('The lists store', () => {
|
||||||
expect(store.allLists[0]).to.eql(modList)
|
expect(store.allLists[0]).to.eql(modList)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a new list with an array of IDs, updating the IDs for existing lists', async ({
|
it('adds a new list with an array of IDs, updating the IDs for existing lists', async () => {
|
||||||
worker,
|
const store = useListsStore()
|
||||||
}) => {
|
|
||||||
const list = { id: '1', accountIds: ['1', '2', '3'] }
|
const list = { id: '1', accountIds: ['1', '2', '3'] }
|
||||||
const modList = { id: '1', accountIds: ['3', '4', '5'] }
|
const modList = { id: '1', accountIds: ['3', '4', '5'] }
|
||||||
|
|
||||||
worker.use(
|
const mockFetch = vi
|
||||||
http.post(MASTODON_LIST_ACCOUNTS_URL(':id'), () =>
|
.fn()
|
||||||
HttpResponse.json({ ok: true }),
|
.mockResolvedValueOnce(
|
||||||
),
|
new Response(JSON.stringify({ ok: true }), {
|
||||||
http.delete(MASTODON_LIST_ACCOUNTS_URL(':id'), () =>
|
headers: { 'Content-Type': 'application/json' },
|
||||||
HttpResponse.json({ ok: true }),
|
}),
|
||||||
),
|
)
|
||||||
)
|
.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify({ ok: true }), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify({ ok: true }), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
await store.setListAccounts({
|
await store.setListAccounts({
|
||||||
listId: list.id,
|
listId: list.id,
|
||||||
accountIds: list.accountIds,
|
accountIds: list.accountIds,
|
||||||
})
|
})
|
||||||
|
expect(mockFetch).to.have.been.calledOnce
|
||||||
|
expect(mockFetch.mock.calls[0][0]).to.eql(MASTODON_LIST_ACCOUNTS_URL('1'))
|
||||||
|
expect(mockFetch.mock.calls[0][1]).to.have.property('method', 'POST')
|
||||||
|
mockFetch.mockClear()
|
||||||
|
|
||||||
expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds)
|
expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds)
|
||||||
|
|
||||||
await store.setListAccounts({
|
await store.setListAccounts({
|
||||||
|
|
@ -80,12 +108,19 @@ describe('The lists store', () => {
|
||||||
accountIds: modList.accountIds,
|
accountIds: modList.accountIds,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(mockFetch).to.have.been.calledTwice
|
||||||
|
expect(mockFetch.mock.calls[0][0]).to.eql(MASTODON_LIST_ACCOUNTS_URL('1'))
|
||||||
|
expect(mockFetch.mock.calls[0][1]).to.have.property('method', 'POST')
|
||||||
|
expect(mockFetch.mock.calls[1][0]).to.eql(MASTODON_LIST_ACCOUNTS_URL('1'))
|
||||||
|
expect(mockFetch.mock.calls[1][1]).to.have.property('method', 'DELETE')
|
||||||
|
|
||||||
expect(store.allListsObject[modList.id].accountIds).to.eql(
|
expect(store.allListsObject[modList.id].accountIds).to.eql(
|
||||||
modList.accountIds,
|
modList.accountIds,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deletes a list', async ({ worker }) => {
|
it('deletes a list', async () => {
|
||||||
|
const store = useListsStore()
|
||||||
store.$patch({
|
store.$patch({
|
||||||
allLists: [{ id: '1', title: 'testList' }],
|
allLists: [{ id: '1', title: 'testList' }],
|
||||||
allListsObject: {
|
allListsObject: {
|
||||||
|
|
@ -94,11 +129,12 @@ describe('The lists store', () => {
|
||||||
})
|
})
|
||||||
const listId = '1'
|
const listId = '1'
|
||||||
|
|
||||||
worker.use(
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
||||||
http.delete(MASTODON_LIST_URL(':id'), () =>
|
new Response(JSON.stringify({ ok: true }), {
|
||||||
HttpResponse.json({ ok: true }),
|
headers: { 'Content-Type': 'application/json' },
|
||||||
),
|
}),
|
||||||
)
|
)
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
await store.deleteList({ listId })
|
await store.deleteList({ listId })
|
||||||
expect(store.allLists).to.have.length(0)
|
expect(store.allLists).to.have.length(0)
|
||||||
|
|
@ -108,6 +144,7 @@ describe('The lists store', () => {
|
||||||
|
|
||||||
describe('getters', () => {
|
describe('getters', () => {
|
||||||
it('returns list title', () => {
|
it('returns list title', () => {
|
||||||
|
const store = useListsStore()
|
||||||
store.$patch({
|
store.$patch({
|
||||||
allLists: [{ id: '1', title: 'testList' }],
|
allLists: [{ id: '1', title: 'testList' }],
|
||||||
allListsObject: {
|
allListsObject: {
|
||||||
|
|
@ -120,6 +157,7 @@ describe('The lists store', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns list accounts', () => {
|
it('returns list accounts', () => {
|
||||||
|
const store = useListsStore()
|
||||||
store.$patch({
|
store.$patch({
|
||||||
allLists: [{ id: '1', title: 'testList' }],
|
allLists: [{ id: '1', title: 'testList' }],
|
||||||
allListsObject: {
|
allListsObject: {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { createTestingPinia } from '@pinia/testing'
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
import { HttpResponse, http } from 'msw'
|
|
||||||
import { setActivePinia } from 'pinia'
|
import { setActivePinia } from 'pinia'
|
||||||
|
|
||||||
import { test as it } from '/test/fixtures/mock_api.js'
|
|
||||||
|
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,51 +9,67 @@ import {
|
||||||
OAUTH_TOKEN_URL,
|
OAUTH_TOKEN_URL,
|
||||||
} from 'src/api/oauth.js'
|
} from 'src/api/oauth.js'
|
||||||
|
|
||||||
const authApis = () => [
|
const response = (data, extra = {}) =>
|
||||||
http.post(MASTODON_APP_URL, () => {
|
new Response(JSON.stringify(data), {
|
||||||
return HttpResponse.json({
|
headers: { 'Content-Type': 'application/json' },
|
||||||
client_id: 'test-id',
|
...extra,
|
||||||
client_secret: 'test-secret',
|
})
|
||||||
})
|
|
||||||
}),
|
|
||||||
http.get(MASTODON_APP_VERIFY_URL, ({ request }) => {
|
|
||||||
const authHeader = request.headers.get('Authorization')
|
|
||||||
if (
|
|
||||||
authHeader === 'Bearer test-app-token' ||
|
|
||||||
authHeader === 'Bearer also-good-app-token'
|
|
||||||
) {
|
|
||||||
return HttpResponse.json({})
|
|
||||||
} else {
|
|
||||||
// Pleroma 2.9.0 gives the following respoonse upon error
|
|
||||||
return HttpResponse.json(
|
|
||||||
{ error: { detail: 'Internal server error' } },
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
http.post(OAUTH_TOKEN_URL, async ({ request }) => {
|
|
||||||
const data = await request.formData()
|
|
||||||
|
|
||||||
if (
|
const defaultMockAppURL = () => {
|
||||||
data.get('client_id') === 'test-id' &&
|
return response({
|
||||||
data.get('client_secret') === 'test-secret' &&
|
client_id: 'test-id',
|
||||||
data.get('grant_type') === 'client_credentials' &&
|
client_secret: 'test-secret',
|
||||||
data.has('redirect_uri')
|
})
|
||||||
) {
|
}
|
||||||
return HttpResponse.json({ access_token: 'test-app-token' })
|
|
||||||
|
const defaultMockAppVerifyURL = (headers) => {
|
||||||
|
const authHeader = headers.Authorization
|
||||||
|
if (
|
||||||
|
authHeader === 'Bearer test-app-token' ||
|
||||||
|
authHeader === 'Bearer also-good-app-token'
|
||||||
|
) {
|
||||||
|
return response({})
|
||||||
|
} else {
|
||||||
|
return response(
|
||||||
|
{ error: { detail: 'Internal server error' } },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMockOAuthTokenURL = (params) => {
|
||||||
|
const data = params.body
|
||||||
|
|
||||||
|
if (
|
||||||
|
data.get('client_id') === 'test-id' &&
|
||||||
|
data.get('client_secret') === 'test-secret' &&
|
||||||
|
data.get('grant_type') === 'client_credentials' &&
|
||||||
|
data.has('redirect_uri')
|
||||||
|
) {
|
||||||
|
return response({ access_token: 'test-app-token' })
|
||||||
|
} else {
|
||||||
|
// Pleroma 2.9.0 gives the following respoonse upon error
|
||||||
|
return response({ error: 'Invalid credentials' }, { status: 400 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const authApis = ({ mockAppURL, mockAppVerifyURL, mockOAuthTokenURL } = {}) => {
|
||||||
|
const mockFetch = vi.fn().mockImplementation((url, params) => {
|
||||||
|
const { method = 'GET', headers } = params
|
||||||
|
|
||||||
|
if (url === MASTODON_APP_URL && method === 'POST') {
|
||||||
|
return (mockAppURL ?? defaultMockAppURL)()
|
||||||
|
} else if (url === MASTODON_APP_VERIFY_URL && method === 'GET') {
|
||||||
|
return (mockAppVerifyURL ?? defaultMockAppVerifyURL)(headers)
|
||||||
|
} else if (url === OAUTH_TOKEN_URL && method === 'POST') {
|
||||||
|
return (mockOAuthTokenURL ?? defaultMockOAuthTokenURL)(params)
|
||||||
} else {
|
} else {
|
||||||
// Pleroma 2.9.0 gives the following respoonse upon error
|
return response({ error: 'Invalid request for test' }, { status: 401 })
|
||||||
return HttpResponse.json(
|
|
||||||
{ error: 'Invalid credentials' },
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
]
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
return mockFetch
|
||||||
|
}
|
||||||
|
|
||||||
describe('oauth store', () => {
|
describe('oauth store', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -64,17 +77,10 @@ describe('oauth store', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('createApp', () => {
|
describe('createApp', () => {
|
||||||
it('should use create an app and record client id and secret', async ({
|
it('should use create an app and record client id and secret', async () => {
|
||||||
worker,
|
authApis()
|
||||||
}) => {
|
|
||||||
worker.use(
|
|
||||||
http.post(MASTODON_APP_URL, () => {
|
|
||||||
return HttpResponse.text('Throttled', { status: 429 })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
worker.use(...authApis())
|
|
||||||
const app = await store.createApp()
|
const app = await store.createApp()
|
||||||
expect(store.clientId).to.eql('test-id')
|
expect(store.clientId).to.eql('test-id')
|
||||||
expect(store.clientSecret).to.eql('test-secret')
|
expect(store.clientSecret).to.eql('test-secret')
|
||||||
|
|
@ -82,10 +88,13 @@ describe('oauth store', () => {
|
||||||
expect(app.clientSecret).to.eql('test-secret')
|
expect(app.clientSecret).to.eql('test-secret')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw and not update if failed', async ({ worker }) => {
|
it('should throw and not update if failed', async () => {
|
||||||
worker.use(
|
const mockFetch = authApis()
|
||||||
http.post(MASTODON_APP_URL, () => {
|
mockFetch.mockResolvedValueOnce(
|
||||||
return HttpResponse.text('Throttled', { status: 429 })
|
new Response('Throttled', {
|
||||||
|
status: 429,
|
||||||
|
statusText: 'Throttled',
|
||||||
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -98,8 +107,8 @@ describe('oauth store', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('ensureApp', () => {
|
describe('ensureApp', () => {
|
||||||
it('should create an app if it does not exist', async ({ worker }) => {
|
it('should create an app if it does not exist', async () => {
|
||||||
worker.use(...authApis())
|
authApis()
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
const app = await store.ensureApp()
|
const app = await store.ensureApp()
|
||||||
expect(store.clientId).to.eql('test-id')
|
expect(store.clientId).to.eql('test-id')
|
||||||
|
|
@ -108,12 +117,15 @@ describe('oauth store', () => {
|
||||||
expect(app.clientSecret).to.eql('test-secret')
|
expect(app.clientSecret).to.eql('test-secret')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not create an app if it exists', async ({ worker }) => {
|
it('should not create an app if it exists', async () => {
|
||||||
worker.use(
|
authApis({
|
||||||
http.post(MASTODON_APP_URL, () => {
|
mockAppURL: () =>
|
||||||
return HttpResponse.text('Should not call this API', { status: 400 })
|
new Response('Throttled', {
|
||||||
}),
|
status: 429,
|
||||||
)
|
statusText: 'Throttled',
|
||||||
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.clientId = 'another-id'
|
store.clientId = 'another-id'
|
||||||
|
|
@ -128,8 +140,8 @@ describe('oauth store', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getAppToken', () => {
|
describe('getAppToken', () => {
|
||||||
it('should get app token and set it in state', async ({ worker }) => {
|
it('should get app token and set it in state', async () => {
|
||||||
worker.use(...authApis())
|
authApis()
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.clientId = 'test-id'
|
store.clientId = 'test-id'
|
||||||
store.clientSecret = 'test-secret'
|
store.clientSecret = 'test-secret'
|
||||||
|
|
@ -139,10 +151,8 @@ describe('oauth store', () => {
|
||||||
expect(store.appToken).to.eql('test-app-token')
|
expect(store.appToken).to.eql('test-app-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw and not set state if it cannot get app token', async ({
|
it('should throw and not set state if it cannot get app token', async () => {
|
||||||
worker,
|
authApis()
|
||||||
}) => {
|
|
||||||
worker.use(...authApis())
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.clientId = 'bad-id'
|
store.clientId = 'bad-id'
|
||||||
store.clientSecret = 'bad-secret'
|
store.clientSecret = 'bad-secret'
|
||||||
|
|
@ -153,16 +163,16 @@ describe('oauth store', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('ensureAppToken', () => {
|
describe('ensureAppToken', () => {
|
||||||
it('should work if the state is empty', async ({ worker }) => {
|
it('should work if the state is empty', async () => {
|
||||||
worker.use(...authApis())
|
authApis()
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
const token = await store.ensureAppToken()
|
const token = await store.ensureAppToken()
|
||||||
expect(token).to.eql('test-app-token')
|
expect(token).to.eql('test-app-token')
|
||||||
expect(store.appToken).to.eql('test-app-token')
|
expect(store.appToken).to.eql('test-app-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work if we already have a working token', async ({ worker }) => {
|
it('should work if we already have a working token', async () => {
|
||||||
worker.use(...authApis())
|
authApis()
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.appToken = 'also-good-app-token'
|
store.appToken = 'also-good-app-token'
|
||||||
|
|
||||||
|
|
@ -171,15 +181,16 @@ describe('oauth store', () => {
|
||||||
expect(store.appToken).to.eql('also-good-app-token')
|
expect(store.appToken).to.eql('also-good-app-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work if we have a bad token but good app credentials', async ({
|
it('should work if we have a bad token but good app credentials', async () => {
|
||||||
worker,
|
authApis({
|
||||||
}) => {
|
mockAppURL: () =>
|
||||||
worker.use(
|
new Response('Should not call this API', {
|
||||||
...authApis(),
|
status: 400,
|
||||||
http.post(MASTODON_APP_URL, () => {
|
statusText: 'Should not call this API',
|
||||||
return HttpResponse.text('Should not call this API', { status: 400 })
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
}),
|
}),
|
||||||
)
|
})
|
||||||
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.appToken = 'bad-app-token'
|
store.appToken = 'bad-app-token'
|
||||||
store.clientId = 'test-id'
|
store.clientId = 'test-id'
|
||||||
|
|
@ -190,15 +201,16 @@ describe('oauth store', () => {
|
||||||
expect(store.appToken).to.eql('test-app-token')
|
expect(store.appToken).to.eql('test-app-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work if we have no token but good app credentials', async ({
|
it('should work if we have no token but good app credentials', async () => {
|
||||||
worker,
|
authApis({
|
||||||
}) => {
|
mockAppURL: () =>
|
||||||
worker.use(
|
new Response('Should not call this API', {
|
||||||
...authApis(),
|
status: 400,
|
||||||
http.post(MASTODON_APP_URL, () => {
|
statusText: 'Should not call this API',
|
||||||
return HttpResponse.text('Should not call this API', { status: 400 })
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
}),
|
}),
|
||||||
)
|
})
|
||||||
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.clientId = 'test-id'
|
store.clientId = 'test-id'
|
||||||
store.clientSecret = 'test-secret'
|
store.clientSecret = 'test-secret'
|
||||||
|
|
@ -208,10 +220,8 @@ describe('oauth store', () => {
|
||||||
expect(store.appToken).to.eql('test-app-token')
|
expect(store.appToken).to.eql('test-app-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work if we have no token and bad app credentials', async ({
|
it('should work if we have no token and bad app credentials', async () => {
|
||||||
worker,
|
authApis()
|
||||||
}) => {
|
|
||||||
worker.use(...authApis())
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.clientId = 'bad-id'
|
store.clientId = 'bad-id'
|
||||||
store.clientSecret = 'bad-secret'
|
store.clientSecret = 'bad-secret'
|
||||||
|
|
@ -223,10 +233,8 @@ describe('oauth store', () => {
|
||||||
expect(store.clientSecret).to.eql('test-secret')
|
expect(store.clientSecret).to.eql('test-secret')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work if we have bad token and bad app credentials', async ({
|
it('should work if we have bad token and bad app credentials', async () => {
|
||||||
worker,
|
authApis()
|
||||||
}) => {
|
|
||||||
worker.use(...authApis())
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
store.appToken = 'bad-app-token'
|
store.appToken = 'bad-app-token'
|
||||||
store.clientId = 'bad-id'
|
store.clientId = 'bad-id'
|
||||||
|
|
@ -239,23 +247,29 @@ describe('oauth store', () => {
|
||||||
expect(store.clientSecret).to.eql('test-secret')
|
expect(store.clientSecret).to.eql('test-secret')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw if we cannot create an app', async ({ worker }) => {
|
it('should throw if we cannot create an app', async () => {
|
||||||
worker.use(
|
authApis({
|
||||||
http.post(MASTODON_APP_URL, () => {
|
mockAppURL: () =>
|
||||||
return HttpResponse.text('Throttled', { status: 429 })
|
new Response('Throttled', {
|
||||||
}),
|
status: 429,
|
||||||
)
|
statusText: 'Throttled',
|
||||||
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
await expect(store.ensureAppToken()).rejects.toThrowError('Throttled')
|
await expect(store.ensureAppToken()).rejects.toThrowError('Throttled')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw if we cannot obtain app token', async ({ worker }) => {
|
it('should throw if we cannot obtain app token', async () => {
|
||||||
worker.use(
|
authApis({
|
||||||
http.post(OAUTH_TOKEN_URL, () => {
|
mockOAuthTokenURL: () =>
|
||||||
return HttpResponse.text('Throttled', { status: 429 })
|
new Response('Throttled', {
|
||||||
}),
|
status: 429,
|
||||||
)
|
statusText: 'Throttled',
|
||||||
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
const store = useOAuthStore()
|
const store = useOAuthStore()
|
||||||
await expect(store.getAppToken()).rejects.toThrowError('Throttled')
|
await expect(store.getAppToken()).rejects.toThrowError('Throttled')
|
||||||
|
|
|
||||||
|
|
@ -476,100 +476,105 @@ describe('Statuses store', () => {
|
||||||
])
|
])
|
||||||
.flat()
|
.flat()
|
||||||
|
|
||||||
it.each(
|
it.each(optimismInteractions)(
|
||||||
optimismInteractions,
|
'%s - optimism call',
|
||||||
)('%s - optimism call', async (method, property, count) => {
|
async (method, property, count) => {
|
||||||
// Prepare our status
|
// Prepare our status
|
||||||
const status = mockStatus()
|
const status = mockStatus()
|
||||||
const negate = method.startsWith('un')
|
const negate = method.startsWith('un')
|
||||||
const oldCount = 9
|
const oldCount = 9
|
||||||
const newCount = negate ? 8 : 10
|
const newCount = negate ? 8 : 10
|
||||||
status[property] = negate
|
status[property] = negate
|
||||||
if (count) {
|
if (count) {
|
||||||
status[count] = oldCount
|
status[count] = oldCount
|
||||||
}
|
}
|
||||||
if (method === 'unbookmark') {
|
if (method === 'unbookmark') {
|
||||||
status.bookmark_folder_id = 'argument'
|
status.bookmark_folder_id = 'argument'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert it
|
// Insert it
|
||||||
const store = useStatusesStore()
|
const store = useStatusesStore()
|
||||||
store.addNewStatuses({
|
store.addNewStatuses({
|
||||||
statuses: [status],
|
statuses: [status],
|
||||||
timestamp: 1,
|
timestamp: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const mockFetch = vi.fn()
|
const mockFetch = vi.fn()
|
||||||
mockFetch.mockResolvedValueOnce(
|
mockFetch.mockResolvedValueOnce(
|
||||||
new Response(JSON.stringify(mockMastoAPIStatus({ text: 'Updated' })), {
|
new Response(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
JSON.stringify(mockMastoAPIStatus({ text: 'Updated' })),
|
||||||
}),
|
{
|
||||||
)
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
},
|
||||||
expect(store.allStatuses.get('1')).to.have.property(property, negate)
|
),
|
||||||
if (count) {
|
|
||||||
expect(store.allStatuses.get('1')).to.have.property(count, oldCount)
|
|
||||||
}
|
|
||||||
vi.stubGlobal('fetch', mockFetch)
|
|
||||||
|
|
||||||
store[method]('1', 'argument')
|
|
||||||
expect(store.allStatuses.get('1')).to.have.property(property, !negate)
|
|
||||||
if (count) {
|
|
||||||
expect(store.allStatuses.get('1')).to.have.property(count, newCount)
|
|
||||||
}
|
|
||||||
if (property === 'bookmarked') {
|
|
||||||
expect(store.allStatuses.get('1')).to.have.property(
|
|
||||||
'bookmark_folder_id',
|
|
||||||
'argument',
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it.each(
|
expect(store.allStatuses.get('1')).to.have.property(property, negate)
|
||||||
optimismInteractions,
|
if (count) {
|
||||||
)('%s - optimism fail', async (method, property, count) => {
|
expect(store.allStatuses.get('1')).to.have.property(count, oldCount)
|
||||||
// Prepare our status
|
}
|
||||||
const status = mockStatus()
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
const negate = method.startsWith('un')
|
|
||||||
const oldCount = 9
|
|
||||||
status[property] = negate
|
|
||||||
if (count) {
|
|
||||||
status[count] = oldCount
|
|
||||||
}
|
|
||||||
if (method === 'unbookmark') {
|
|
||||||
status.bookmark_folder_id = 'argument'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert it
|
store[method]('1', 'argument')
|
||||||
const store = useStatusesStore()
|
expect(store.allStatuses.get('1')).to.have.property(property, !negate)
|
||||||
store.addNewStatuses({
|
if (count) {
|
||||||
statuses: [status],
|
expect(store.allStatuses.get('1')).to.have.property(count, newCount)
|
||||||
timestamp: 1,
|
}
|
||||||
})
|
if (property === 'bookmarked') {
|
||||||
|
expect(store.allStatuses.get('1')).to.have.property(
|
||||||
|
'bookmark_folder_id',
|
||||||
|
'argument',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const mockFetch = vi.fn()
|
it.each(optimismInteractions)(
|
||||||
mockFetch.mockRejectedValueOnce(new Error('Failure!'))
|
'%s - optimism fail',
|
||||||
|
async (method, property, count) => {
|
||||||
|
// Prepare our status
|
||||||
|
const status = mockStatus()
|
||||||
|
const negate = method.startsWith('un')
|
||||||
|
const oldCount = 9
|
||||||
|
status[property] = negate
|
||||||
|
if (count) {
|
||||||
|
status[count] = oldCount
|
||||||
|
}
|
||||||
|
if (method === 'unbookmark') {
|
||||||
|
status.bookmark_folder_id = 'argument'
|
||||||
|
}
|
||||||
|
|
||||||
expect(store.allStatuses.get('1')).to.have.property(property, negate)
|
// Insert it
|
||||||
if (count) {
|
const store = useStatusesStore()
|
||||||
expect(store.allStatuses.get('1')).to.have.property(count, oldCount)
|
store.addNewStatuses({
|
||||||
}
|
statuses: [status],
|
||||||
vi.stubGlobal('fetch', mockFetch)
|
timestamp: 1,
|
||||||
|
})
|
||||||
|
|
||||||
await store[method]('1', 'argument')
|
const mockFetch = vi.fn()
|
||||||
expect(store.allStatuses.get('1')).to.have.property(property, negate)
|
mockFetch.mockRejectedValueOnce(new Error('Failure!'))
|
||||||
if (count) {
|
|
||||||
expect(store.allStatuses.get('1')).to.have.property(count, oldCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Failing 'bookmark' method SHOULD clear folder id
|
expect(store.allStatuses.get('1')).to.have.property(property, negate)
|
||||||
if (method === 'unbookmark') {
|
if (count) {
|
||||||
expect(store.allStatuses.get('1')).to.have.property(
|
expect(store.allStatuses.get('1')).to.have.property(count, oldCount)
|
||||||
'bookmark_folder_id',
|
}
|
||||||
'argument',
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
)
|
|
||||||
}
|
await store[method]('1', 'argument')
|
||||||
})
|
expect(store.allStatuses.get('1')).to.have.property(property, negate)
|
||||||
|
if (count) {
|
||||||
|
expect(store.allStatuses.get('1')).to.have.property(count, oldCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failing 'bookmark' method SHOULD clear folder id
|
||||||
|
if (method === 'unbookmark') {
|
||||||
|
expect(store.allStatuses.get('1')).to.have.property(
|
||||||
|
'bookmark_folder_id',
|
||||||
|
'argument',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['reactWithEmoji', 0],
|
['reactWithEmoji', 0],
|
||||||
|
|
|
||||||
|
|
@ -1180,28 +1180,27 @@ describe('Users store', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.each([
|
it.each(['unmute', 'unblock', 'removeUserFromFollowers'])(
|
||||||
'unmute',
|
'%s',
|
||||||
'unblock',
|
async (action) => {
|
||||||
'removeUserFromFollowers',
|
const mockFetch = vi.fn().mockResolvedValueOnce(
|
||||||
])('%s', async (action) => {
|
new Response(JSON.stringify({ id: userId }), {
|
||||||
const mockFetch = vi.fn().mockResolvedValueOnce(
|
headers: { 'Content-Type': 'application/json' },
|
||||||
new Response(JSON.stringify({ id: userId }), {
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' },
|
)
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
vi.stubGlobal('fetch', mockFetch)
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
const store = useUsersStore()
|
const store = useUsersStore()
|
||||||
const { storeAction, apiUrl } = actionKeys(action)
|
const { storeAction, apiUrl } = actionKeys(action)
|
||||||
await store[storeAction](userId)
|
await store[storeAction](userId)
|
||||||
|
|
||||||
expect(mockFetch).to.have.been.calledWith(
|
expect(mockFetch).to.have.been.calledWith(
|
||||||
USER_API[apiUrl](userId),
|
USER_API[apiUrl](userId),
|
||||||
DEFAULT_OPTIONS(),
|
DEFAULT_OPTIONS(),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
describe.each(['mute', 'block'])('%s', (action) => {
|
describe.each(['mute', 'block'])('%s', (action) => {
|
||||||
it('normal', async () => {
|
it('normal', async () => {
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ import vue from '@vitejs/plugin-vue'
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
import { playwright } from '@vitest/browser-playwright'
|
import { playwright } from '@vitest/browser-playwright'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import eslint from 'vite-plugin-eslint2'
|
import biomePlugin from 'vite-plugin-biome'
|
||||||
import stylelint from 'vite-plugin-stylelint'
|
import stylelint from 'vite-plugin-stylelint'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
import { configDefaults } from 'vitest/config'
|
import { configDefaults } from 'vitest/config'
|
||||||
|
|
||||||
import { getCommitHash } from './build/commit_hash.js'
|
import { getCommitHash } from './build/commit_hash.js'
|
||||||
import copyPlugin from './build/copy_plugin.js'
|
import copyPlugin from './build/copy_plugin.js'
|
||||||
import emojisPlugin from './build/emojis_plugin.js'
|
import emojisPlugin from './build/emojis_plugin.js'
|
||||||
import mswPlugin from './build/msw_plugin.js'
|
|
||||||
import { buildSwPlugin, swMessagesPlugin } from './build/sw_plugin.js'
|
import { buildSwPlugin, swMessagesPlugin } from './build/sw_plugin.js'
|
||||||
|
|
||||||
const localConfigPath = '<projectRoot>/config/local.json'
|
const localConfigPath = '<projectRoot>/config/local.json'
|
||||||
|
|
@ -142,6 +142,7 @@ export default defineConfig(async ({ mode, command }) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
vueDevTools(),
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
buildSwPlugin({ swSrc, swDest }),
|
buildSwPlugin({ swSrc, swDest }),
|
||||||
swMessagesPlugin(),
|
swMessagesPlugin(),
|
||||||
|
|
@ -150,10 +151,8 @@ export default defineConfig(async ({ mode, command }) => {
|
||||||
inUrl: '/static/ruffle',
|
inUrl: '/static/ruffle',
|
||||||
inFs: resolve(projectRoot, 'node_modules/@ruffle-rs/ruffle'),
|
inFs: resolve(projectRoot, 'node_modules/@ruffle-rs/ruffle'),
|
||||||
}),
|
}),
|
||||||
eslint({
|
biomePlugin({
|
||||||
lintInWorker: true,
|
mode: 'check',
|
||||||
lintOnStart: true,
|
|
||||||
cacheLocation: resolve(projectRoot, 'node_modules/.cache/eslintcache'),
|
|
||||||
}),
|
}),
|
||||||
stylelint({
|
stylelint({
|
||||||
lintInWorker: true,
|
lintInWorker: true,
|
||||||
|
|
@ -163,7 +162,6 @@ export default defineConfig(async ({ mode, command }) => {
|
||||||
'node_modules/.cache/stylelintcache',
|
'node_modules/.cache/stylelintcache',
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
...(mode === 'test' ? [mswPlugin()] : []),
|
|
||||||
],
|
],
|
||||||
css: {
|
css: {
|
||||||
devSourcemap: true,
|
devSourcemap: true,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue