Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
251b16cb62 | ||
|
|
da80c599bf | ||
|
|
ca6a900874 | ||
|
|
5e09e96f7b | ||
|
|
068500df84 | ||
|
|
15949a7e21 | ||
|
|
e635fe3378 | ||
|
|
8276c5db57 | ||
|
|
1d9876a6eb | ||
|
|
6340174927 | ||
|
|
7b031b228e | ||
|
|
ec408940e4 | ||
|
|
bd46fc11f4 | ||
|
|
42e55fe248 | ||
|
|
3a9011b3cb | ||
|
|
e3bf0442e8 | ||
|
|
0413100e2b | ||
|
|
7cad04bbf4 | ||
|
|
c9f0bbf26a | ||
|
|
d91639172a | ||
|
|
5f0cc1becd | ||
|
|
a8bcab7770 | ||
|
|
b1682e4f54 | ||
|
|
2447b34107 | ||
|
|
8126d19682 | ||
|
|
6cc8c81e3d | ||
|
|
e86d812ee6 | ||
|
|
c6f042830f | ||
|
|
2378c0c441 | ||
|
|
7bc53d7bc6 | ||
|
|
7918d6c0d1 | ||
|
|
073e8b897b | ||
|
|
46a3006f4d | ||
|
|
5d578ab9e6 | ||
|
|
f3fe729900 | ||
|
|
a04c157770 | ||
|
|
320ffcb434 | ||
|
|
37a2fb17b5 | ||
|
|
b5936cec54 | ||
|
|
ea61c81cce | ||
|
|
64d71d0d20 | ||
|
|
af08b4c004 | ||
|
|
ea670b6ae6 | ||
|
|
f19e354418 | ||
|
|
b54e3dd243 | ||
|
|
9798e39e49 | ||
|
|
66fd0c19da | ||
|
|
8d6c67206c | ||
|
|
8cf683eb50 | ||
|
|
580a23f3f5 | ||
|
|
29dc46a993 | ||
|
|
93885bad7b | ||
|
|
6e0619c21e | ||
|
|
8c956afabd | ||
|
|
f9fbf62283 | ||
|
|
5f1f536181 | ||
|
|
ba59514464 | ||
|
|
6bb6750be7 | ||
|
|
19a3df6d47 | ||
|
|
87408fd2a9 | ||
|
|
9ee39f45c9 | ||
|
|
a763304386 | ||
|
|
f8ce501b53 | ||
|
|
54d449647c | ||
|
|
552fd39d74 | ||
|
|
01d73f2753 | ||
|
|
75da496128 | ||
|
|
fd9b01ca27 | ||
|
|
1a82f3fff1 | ||
|
|
8e6f051dec | ||
|
|
8ceff03f3e | ||
|
|
e1bbcc415f | ||
|
|
567ba7d962 | ||
|
|
153416604d | ||
|
|
caa882dc37 | ||
|
|
9c05788834 | ||
|
|
6b585ef9a7 | ||
|
|
469ddacfcc | ||
|
|
4d4e3bfb8a | ||
|
|
257a7530a0 | ||
|
|
02126a1e24 | ||
|
|
97913c390b | ||
|
|
c1cf58b99e | ||
|
|
574b0ce84b | ||
|
|
26ac896bac | ||
|
|
b36babf754 | ||
|
|
0d69b895aa | ||
|
|
3b48ca0f4b | ||
|
|
2e6ce16b11 | ||
|
|
edeec49121 | ||
|
|
5de716b19d | ||
|
|
cb2b933a90 | ||
|
|
37f99988eb | ||
|
|
99962dc3c6 | ||
|
|
063d54dfae | ||
|
|
59f79be290 | ||
|
|
813c8e6b86 | ||
|
|
23e3650570 | ||
|
|
092aefdd71 | ||
|
|
19361e2c68 | ||
|
|
6e48f02d25 | ||
|
|
46e45c3715 | ||
|
|
eca495ce63 | ||
|
|
70e72d843e | ||
|
|
7ac3b72a4f | ||
|
|
cc9072e9cc | ||
|
|
a0ee47b809 | ||
|
|
af40ff8eb1 | ||
|
|
b0211be03c | ||
|
|
3174ffed5c | ||
|
|
99c7252a15 | ||
|
|
99c81fed56 | ||
|
|
a01fc5b572 | ||
|
|
1b0bb1d5ff | ||
|
|
1ffd653515 | ||
|
|
e1c60084d3 | ||
|
|
4c6b6ffdac | ||
|
|
783d458100 | ||
|
|
9b7d38bca7 | ||
|
|
a8a8502577 | ||
|
|
6a1619d300 | ||
|
|
9b811f0166 | ||
|
|
7e1b77f872 | ||
|
|
b7c598ea56 | ||
|
|
da7c993fd6 | ||
|
|
c6dd5db6da |
No files matched your search
@@ -58,19 +58,24 @@ runs:
|
||||
|
||||
- name: Install prerequisites
|
||||
working-directory: frontend
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||
vcpkg integrate install
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
echo 'do nothing'
|
||||
fi
|
||||
cargo make appflowy-flutter-deps-tools
|
||||
shell: bash
|
||||
run: |
|
||||
case $RUNNER_OS in
|
||||
Linux)
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
;;
|
||||
Windows)
|
||||
vcpkg integrate install
|
||||
vcpkg update
|
||||
;;
|
||||
macOS)
|
||||
# No additional prerequisites needed for macOS
|
||||
;;
|
||||
esac
|
||||
cargo make appflowy-flutter-deps-tools
|
||||
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
@@ -94,4 +99,4 @@ runs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
||||
path: appflowy_flutter.tar.gz
|
||||
path: appflowy_flutter.tar.gz
|
||||
@@ -52,7 +52,7 @@ runs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager libmpv-dev mpv
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager
|
||||
shell: bash
|
||||
|
||||
- name: Enable Flutter Desktop
|
||||
@@ -75,4 +75,4 @@ runs:
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
sudo apt-get install network-manager
|
||||
flutter test ${{ inputs.test_path }} -d Linux --coverage
|
||||
shell: bash
|
||||
shell: bash
|
||||
@@ -2,18 +2,10 @@ name: Docker-CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
paths:
|
||||
- frontend/**
|
||||
branches: ["main", "release/*"]
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
paths:
|
||||
- frontend/**
|
||||
types: [ opened, synchronize, reopened, unlocked, ready_for_review ]
|
||||
branches: ["main", "release/*"]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -27,25 +19,29 @@ jobs:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Compose
|
||||
run: |
|
||||
docker-compose --version || {
|
||||
echo "Docker Compose not found, installing..."
|
||||
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# cache the docker layers
|
||||
# don't cache anything temporarly, because it always triggers "no space left on device" error
|
||||
# - name: Cache Docker layers
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: /tmp/.buildx-cache
|
||||
# key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-buildx-
|
||||
|
||||
- name: Build the app
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu -o pipefail
|
||||
cd frontend/scripts/docker-buildfiles
|
||||
docker-compose build --no-cache --progress=plain \
|
||||
| while read line; do \
|
||||
if [[ "$line" =~ ^Step[[:space:]] ]]; then \
|
||||
echo "$(date -u '+%H:%M:%S') | $line"; \
|
||||
else \
|
||||
echo "$line"; \
|
||||
fi; \
|
||||
done
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./frontend/scripts/docker-buildfiles/Dockerfile
|
||||
push: false
|
||||
# cache-from: type=local,src=/tmp/.buildx-cache
|
||||
# cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||
|
||||
# - name: Move cache
|
||||
# run: |
|
||||
# rm -rf /tmp/.buildx-cache
|
||||
# mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
@@ -25,9 +25,10 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
CARGO_MAKE_VERSION: "0.36.6"
|
||||
FLUTTER_VERSION: "3.22.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
CARGO_MAKE_VERSION: "0.37.18"
|
||||
CLOUD_VERSION: 0.6.34-amd64
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -39,7 +40,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@@ -73,7 +74,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ windows-latest ]
|
||||
os: [windows-latest]
|
||||
include:
|
||||
- os: windows-latest
|
||||
flutter_profile: development-windows-x86
|
||||
@@ -100,7 +101,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ macos-latest ]
|
||||
os: [macos-latest]
|
||||
include:
|
||||
- os: macos-latest
|
||||
flutter_profile: development-mac-x86_64
|
||||
@@ -122,12 +123,12 @@ jobs:
|
||||
flutter_profile: ${{ matrix.flutter_profile }}
|
||||
|
||||
unit_test:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@@ -173,7 +174,7 @@ jobs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
@@ -216,11 +217,11 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
cloud_integration_test:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@@ -241,12 +242,15 @@ jobs:
|
||||
cp deploy.env .env
|
||||
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
|
||||
sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env
|
||||
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
|
||||
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
|
||||
|
||||
- name: Run Docker-Compose
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
run: |
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
@@ -289,7 +293,7 @@ jobs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
shell: bash
|
||||
|
||||
- name: Enable Flutter Desktop
|
||||
@@ -317,31 +321,25 @@ jobs:
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
sudo apt-get install network-manager
|
||||
docker ps -a
|
||||
flutter test integration_test/cloud/cloud_runner.dart -d Linux --coverage
|
||||
flutter test integration_test/desktop/cloud/cloud_runner.dart -d Linux --coverage
|
||||
shell: bash
|
||||
|
||||
# split the integration tests into different machines to minimize the time
|
||||
integration_test_1:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 1
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
@@ -352,26 +350,20 @@ jobs:
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_2:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 2
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
@@ -382,26 +374,20 @@ jobs:
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_3:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 3
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
@@ -409,4 +395,28 @@ jobs:
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_4:
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Flutter Integration Test 4
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
test_path: integration_test/desktop_runner_4.dart
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
@@ -22,6 +22,7 @@ on:
|
||||
env:
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
CLOUD_VERSION: 0.6.34-amd64
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -48,13 +49,13 @@ jobs:
|
||||
model: "iPhone 15"
|
||||
shutdown_after_job: false
|
||||
|
||||
build-macos:
|
||||
integration-tests:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -96,11 +97,11 @@ jobs:
|
||||
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
|
||||
cargo make --profile development-ios-arm64-sim code_generation
|
||||
|
||||
- uses: futureware-tech/simulator-action@v3
|
||||
id: simulator-action
|
||||
with:
|
||||
model: "iPhone 15"
|
||||
shutdown_after_job: false
|
||||
# - uses: futureware-tech/simulator-action@v3
|
||||
# id: simulator-action
|
||||
# with:
|
||||
# model: "iPhone 15"
|
||||
# shutdown_after_job: false
|
||||
|
||||
# - name: Run AppFlowy on simulator
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
@@ -111,7 +112,6 @@ jobs:
|
||||
# kill $pid
|
||||
# continue-on-error: true
|
||||
|
||||
# enable it again if the 12 mins timeout is fixed
|
||||
# - name: Run integration tests
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
# run: flutter test integration_test/runner.dart -d ${{ steps.simulator-action.outputs.udid }}
|
||||
@@ -232,10 +232,10 @@ jobs:
|
||||
matrix:
|
||||
job:
|
||||
- {
|
||||
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
|
||||
os: macos-latest,
|
||||
extra-build-args: "",
|
||||
}
|
||||
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
|
||||
os: macos-latest,
|
||||
extra-build-args: "",
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
@@ -336,12 +336,12 @@ jobs:
|
||||
matrix:
|
||||
job:
|
||||
- {
|
||||
arch: x86_64,
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
os: ubuntu-20.04,
|
||||
extra-build-args: "",
|
||||
flutter_profile: production-linux-x86_64,
|
||||
}
|
||||
arch: x86_64,
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
os: ubuntu-20.04,
|
||||
extra-build-args: "",
|
||||
flutter_profile: production-linux-x86_64,
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
@@ -368,7 +368,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||
sudo apt-get install keybinder-3.0
|
||||
sudo apt-get install -y alien libnotify-dev libmpv-dev mpv
|
||||
sudo apt-get install -y alien libnotify-dev
|
||||
source $HOME/.cargo/env
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
|
||||
@@ -20,6 +20,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CLOUD_VERSION: 0.6.34-amd64
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
|
||||
jobs:
|
||||
@@ -46,7 +47,9 @@ jobs:
|
||||
- name: Ensure AppFlowy-Cloud is Running with Correct Version
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
run: |
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
@@ -91,6 +94,11 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set timezone for action
|
||||
uses: szenius/set-timezone@v2.0
|
||||
with:
|
||||
timezoneLinux: "US/Pacific"
|
||||
|
||||
- name: Maximize build space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
@@ -127,12 +135,15 @@ jobs:
|
||||
run: |
|
||||
cp deploy.env .env
|
||||
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
|
||||
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
|
||||
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
|
||||
|
||||
- name: Ensure AppFlowy-Cloud is Running with Correct Version
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
run: |
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
|
||||
+34
-1
@@ -1,4 +1,37 @@
|
||||
# Release Notes
|
||||
## Version 0.7.2 - 22/10/2024
|
||||
### New Features
|
||||
- Copy link to block
|
||||
- Support turn into in document
|
||||
- Enable sharing links and publishing pages on mobile
|
||||
- Enable drag and drop in row documents
|
||||
- Right-click on page in sidebar to open more actions
|
||||
- Create new subpage in document using `+` character
|
||||
- Allow reordering checklist item
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed issue with inability to cancel inline code format in French IME
|
||||
- Fixed delete with Shift or Ctrl shortcuts not working in documents
|
||||
- Fixed the issues with incorrect time zone being used in filters.
|
||||
|
||||
## Version 0.7.1 - 07/10/2024
|
||||
### New Features
|
||||
- Copy link to share and open it in a browser
|
||||
- Enable the ability to edit the page title within the body of the document
|
||||
- Filter by last modified, created at, or a date range
|
||||
- Allow customization of database property icons
|
||||
- Support CTRL/CMD+X to delete the current line when the selection is collapsed in the document
|
||||
- Support window tiling on macOS
|
||||
- Add filters to grid views on mobile
|
||||
- Create and manage workspaces on mobile
|
||||
- Automatically convert property types for imported CSV files
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed calculations with filters applied
|
||||
- Fixed issues with importing data folders into a cloud account
|
||||
- Fixed French IME backtick issues
|
||||
- Fixed selection gesture bugs on mobile
|
||||
|
||||
## Version 0.7.0 - 19/09/2024
|
||||
### New Features
|
||||
- Support reordering blocks in document with drag and drop
|
||||
@@ -42,7 +75,7 @@
|
||||
- Fixed the inability to edit group names on Kanban boards
|
||||
- Made error codes more user-friendly
|
||||
- Added leading zeros to day and month in date format
|
||||
|
||||
|
||||
## Version 0.6.8 - 22/08/2024
|
||||
### New Features
|
||||
- Enabled viewing data inside a database record on mobile.
|
||||
|
||||
@@ -119,7 +119,7 @@ We do not claim to outperform Notion in terms of functionality and design, at le
|
||||
|
||||
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information.
|
||||
|
||||
## Acknowledgements
|
||||
## Acknowledgments
|
||||
|
||||
Special thanks to these amazing projects which help power AppFlowy.IO:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.7.0"
|
||||
APPFLOWY_VERSION = "0.7.2"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
@@ -16,17 +6,6 @@ analyzer:
|
||||
- "**/*.freezed.dart"
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
- require_trailing_commas
|
||||
|
||||
@@ -51,8 +30,5 @@ linter:
|
||||
- sort_constructors_first
|
||||
- unawaited_futures
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
@@ -1,29 +0,0 @@
|
||||
import 'anon_user_continue_test.dart' as anon_user_continue_test;
|
||||
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
||||
import 'document/document_drag_block_test.dart' as document_drag_block_test;
|
||||
import 'empty_test.dart' as preset_af_cloud_env_test;
|
||||
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
|
||||
import 'user_setting_sync_test.dart' as user_sync_test;
|
||||
import 'workspace/change_name_and_icon_test.dart'
|
||||
as change_workspace_name_and_icon_test;
|
||||
import 'workspace/collaborative_workspace_test.dart'
|
||||
as collaboration_workspace_test;
|
||||
import 'workspace/workspace_settings_test.dart' as workspace_settings_test;
|
||||
|
||||
Future<void> main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
appflowy_cloud_auth_test.main();
|
||||
user_sync_test.main();
|
||||
anon_user_continue_test.main();
|
||||
|
||||
// workspace
|
||||
collaboration_workspace_test.main();
|
||||
change_workspace_name_and_icon_test.main();
|
||||
workspace_settings_test.main();
|
||||
|
||||
// document
|
||||
document_drag_block_test.main();
|
||||
|
||||
// sidebar
|
||||
sidebar_move_page_test.main();
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/constants.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document delete block: ', () {
|
||||
testWidgets('hover on the block and delete it', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before delete
|
||||
final path = [1];
|
||||
final beforeDeletedBlock = tester.editor.getNodeAtPath(path);
|
||||
|
||||
// hover on the block and delete it
|
||||
final optionButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DraggableOptionButton &&
|
||||
widget.blockComponentContext.node.path.equals(path),
|
||||
);
|
||||
|
||||
await tester.hoverOnWidget(
|
||||
optionButton,
|
||||
onHover: () async {
|
||||
// click the delete button
|
||||
await tester.tapButton(optionButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// click the delete button
|
||||
final deleteButton =
|
||||
find.findTextInFlowyText(LocaleKeys.button_delete.tr());
|
||||
await tester.tapButton(deleteButton);
|
||||
|
||||
// wait for the deletion
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is deleted
|
||||
final afterDeletedBlock = tester.editor.getNodeAtPath([1]);
|
||||
expect(afterDeletedBlock.id, isNot(equals(beforeDeletedBlock.id)));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
// import 'package:appflowy/env/cloud_env.dart';
|
||||
// import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
// import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
// import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
// import 'package:integration_test/integration_test.dart';
|
||||
|
||||
// import '../shared/util.dart';
|
||||
|
||||
// void main() {
|
||||
// IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// group('supabase auth', () {
|
||||
// testWidgets('sign in with supabase', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
// await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
// });
|
||||
|
||||
// testWidgets('sign out with supabase', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.account);
|
||||
// await tester.logout();
|
||||
|
||||
// // Go to the sign in page again
|
||||
// await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
// tester.expectToSeeGoogleLoginButton();
|
||||
// });
|
||||
|
||||
// testWidgets('sign in as anonymous', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapSignInAsGuest();
|
||||
|
||||
// // should not see the sync setting page when sign in as anonymous
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.account);
|
||||
|
||||
// // Scroll to sign-out
|
||||
// await tester.scrollUntilVisible(
|
||||
// find.byType(SignInOutButton),
|
||||
// 100,
|
||||
// scrollable: find.findSettingsScrollable(),
|
||||
// );
|
||||
// await tester.tapButton(find.byType(SignInOutButton));
|
||||
|
||||
// tester.expectToSeeGoogleLoginButton();
|
||||
// });
|
||||
|
||||
// // testWidgets('enable encryption', (tester) async {
|
||||
// // await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
// // await tester.tapGoogleLoginInButton();
|
||||
|
||||
// // // Open the setting page and sign out
|
||||
// // await tester.openSettings();
|
||||
// // await tester.openSettingsPage(SettingsPage.cloud);
|
||||
|
||||
// // // the switch should be off by default
|
||||
// // tester.assertEnableEncryptSwitchValue(false);
|
||||
// // await tester.toggleEnableEncrypt();
|
||||
|
||||
// // // the switch should be on after toggling
|
||||
// // tester.assertEnableEncryptSwitchValue(true);
|
||||
|
||||
// // // the switch can not be toggled back to off
|
||||
// // await tester.toggleEnableEncrypt();
|
||||
// // tester.assertEnableEncryptSwitchValue(true);
|
||||
// // });
|
||||
|
||||
// testWidgets('enable sync', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.cloud);
|
||||
|
||||
// // the switch should be on by default
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
// await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
|
||||
// // the switch should be off
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(false);
|
||||
|
||||
// // the switch should be on after toggling
|
||||
// await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'document/document_test_runner.dart' as document_test_runner;
|
||||
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
|
||||
import 'uncategorized/uncategorized_test_runner.dart'
|
||||
as uncategorized_test_runner;
|
||||
import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
|
||||
import 'data_migration/data_migration_test_runner.dart'
|
||||
as data_migration_test_runner;
|
||||
import 'set_env.dart' as preset_af_cloud_env_test;
|
||||
|
||||
Future<void> main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
|
||||
data_migration_test_runner.main();
|
||||
// uncategorized
|
||||
uncategorized_test_runner.main();
|
||||
|
||||
// workspace
|
||||
workspace_test_runner.main();
|
||||
|
||||
// document
|
||||
document_test_runner.main();
|
||||
|
||||
// sidebar
|
||||
sidebar_move_page_test.main();
|
||||
}
|
||||
+15
-15
@@ -24,16 +24,16 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../desktop/board/board_hide_groups_test.dart';
|
||||
import '../shared/dir.dart';
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('appflowy cloud', () {
|
||||
testWidgets('anon user and then sign in', (tester) async {
|
||||
testWidgets('anon user -> sign in -> open imported space', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
@@ -42,6 +42,10 @@ void main() {
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const pageName = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
tester.expectToSeePageName(pageName);
|
||||
|
||||
// rename the name of the anon user
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
@@ -60,20 +64,16 @@ void main() {
|
||||
|
||||
// sign up with Google
|
||||
await tester.tapGoogleLoginInButton();
|
||||
// await tester.pumpAndSettle(const Duration(seconds: 16));
|
||||
|
||||
// sign out
|
||||
// open the imported space
|
||||
await tester.expectToSeeHomePage();
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
await tester.clickSpaceHeader();
|
||||
|
||||
// Scroll to sign-out
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
// After import the anon user data, we will create a new space for it
|
||||
await tester.openSpace("Getting started");
|
||||
await tester.openPage(pageName);
|
||||
|
||||
await tester.logout();
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import 'anon_user_data_migration_test.dart' as anon_user_test;
|
||||
|
||||
void main() async {
|
||||
anon_user_test.main();
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/document_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// copy link to block
|
||||
group('copy link to block:', () {
|
||||
testWidgets('copy link to check if the clipboard has the correct content',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check the clipboard
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text,
|
||||
matches(appflowySharePageLinkPattern),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('copy link to block(another page) and paste it in doc',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
|
||||
// create a new page and paste it
|
||||
const pageName = 'copy link to block';
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// paste the link to the new page
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.paste();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
final node = tester.editor.getNodeAtPath([0]);
|
||||
final delta = node.delta!;
|
||||
final insert = (delta.first as TextInsert).text;
|
||||
final attributes = delta.first.attributes;
|
||||
expect(insert, MentionBlockKeys.mentionChar);
|
||||
final mention =
|
||||
attributes?[MentionBlockKeys.mention] as Map<String, dynamic>;
|
||||
expect(mention[MentionBlockKeys.type], MentionType.page.name);
|
||||
expect(mention[MentionBlockKeys.blockId], isNotNull);
|
||||
expect(mention[MentionBlockKeys.pageId], isNotNull);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
Constants.gettingStartedPageName,
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
// the pasted block content is 'Welcome to AppFlowy'
|
||||
'Welcome to AppFlowy',
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// tap the mention block to jump to the page
|
||||
await tester.tapButton(find.byType(MentionPageBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to go to the getting started page
|
||||
final documentPage = find.byType(DocumentPage);
|
||||
expect(documentPage, findsOneWidget);
|
||||
expect(
|
||||
tester.widget<DocumentPage>(documentPage).view.name,
|
||||
Constants.gettingStartedPageName,
|
||||
);
|
||||
// and the block is selected
|
||||
expect(
|
||||
tester.widget<DocumentPage>(documentPage).initialBlockId,
|
||||
mention[MentionBlockKeys.blockId],
|
||||
);
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('copy link to block(same page) and paste it in doc',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// create a new page and paste it
|
||||
const pageName = 'copy link to block';
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// copy the link to block from the first line
|
||||
const inputText = 'Hello World';
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText(inputText);
|
||||
await tester.ime.insertCharacter('\n');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
|
||||
// paste the link to the second line
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
await tester.editor.paste();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
final node = tester.editor.getNodeAtPath([1]);
|
||||
final delta = node.delta!;
|
||||
final insert = (delta.first as TextInsert).text;
|
||||
final attributes = delta.first.attributes;
|
||||
expect(insert, MentionBlockKeys.mentionChar);
|
||||
final mention =
|
||||
attributes?[MentionBlockKeys.mention] as Map<String, dynamic>;
|
||||
expect(mention[MentionBlockKeys.type], MentionType.page.name);
|
||||
expect(mention[MentionBlockKeys.blockId], isNotNull);
|
||||
expect(mention[MentionBlockKeys.pageId], isNotNull);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
inputText,
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// edit the pasted block
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('!');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
'$inputText!',
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// tap the mention block
|
||||
await tester.tapButton(find.byType(MentionPageBlock));
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('''1. copy link to block from another page
|
||||
2. paste the link to the new page
|
||||
3. delete the original page
|
||||
4. check the content of the block, it should be no access to the page
|
||||
''', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
|
||||
// create a new page and paste it
|
||||
const pageName = 'copy link to block';
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// paste the link to the new page
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.paste();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// tap the mention block to jump to the page
|
||||
await tester.tapButton(find.byType(MentionPageBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to go to the getting started page
|
||||
final documentPage = find.byType(DocumentPage);
|
||||
expect(documentPage, findsOneWidget);
|
||||
expect(
|
||||
tester.widget<DocumentPage>(documentPage).view.name,
|
||||
Constants.gettingStartedPageName,
|
||||
);
|
||||
// delete the getting started page
|
||||
await tester.hoverOnPageName(
|
||||
Constants.gettingStartedPageName,
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
tester.expectToSeeDocumentBanner();
|
||||
tester.expectNotToSeePageName(gettingStarted);
|
||||
|
||||
// delete the page permanently
|
||||
await tester.tapDeletePermanentlyButton();
|
||||
|
||||
// go back the page
|
||||
await tester.openPage(pageName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
// it should be no access to the page
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.findTextInFlowyText(
|
||||
LocaleKeys.document_mention_noAccess.tr(),
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+50
-3
@@ -1,15 +1,19 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/constants.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document drag block: ', () {
|
||||
group('document option actions:', () {
|
||||
testWidgets('drag block to the top', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
@@ -63,5 +67,48 @@ void main() {
|
||||
final afterMoveBlock = tester.editor.getNodeAtPath([9, 0]);
|
||||
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
|
||||
});
|
||||
|
||||
testWidgets('hover on the block and delete it', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before delete
|
||||
final path = [1];
|
||||
final beforeDeletedBlock = tester.editor.getNodeAtPath(path);
|
||||
|
||||
// hover on the block and delete it
|
||||
final optionButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DraggableOptionButton &&
|
||||
widget.blockComponentContext.node.path.equals(path),
|
||||
);
|
||||
|
||||
await tester.hoverOnWidget(
|
||||
optionButton,
|
||||
onHover: () async {
|
||||
// click the delete button
|
||||
await tester.tapButton(optionButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// click the delete button
|
||||
final deleteButton =
|
||||
find.findTextInFlowyText(LocaleKeys.button_delete.tr());
|
||||
await tester.tapButton(deleteButton);
|
||||
|
||||
// wait for the deletion
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is deleted
|
||||
final afterDeletedBlock = tester.editor.getNodeAtPath([1]);
|
||||
expect(afterDeletedBlock.id, isNot(equals(beforeDeletedBlock.id)));
|
||||
});
|
||||
});
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_copy_link_to_block_test.dart'
|
||||
as document_copy_link_to_block_test;
|
||||
import 'document_option_actions_test.dart' as document_option_actions_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
document_option_actions_test.main();
|
||||
document_copy_link_to_block_test.main();
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../shared/util.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
// This test is meaningless, just for preventing the CI from failing.
|
||||
void main() {
|
||||
+15
-6
@@ -18,18 +18,19 @@ import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../shared/constants.dart';
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -92,6 +93,14 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
// Attempt right-click on the page name and expect not to see
|
||||
await tester.tap(gettingStarted.last, buttons: kSecondaryButton);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.text(LocaleKeys.disclosureAction_moveTo.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// move the current page to Getting started
|
||||
await tester.tapButton(
|
||||
gettingStarted.last,
|
||||
+2
-2
@@ -15,8 +15,8 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
+3
-3
@@ -17,9 +17,9 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../shared/dir.dart';
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
||||
import 'user_setting_sync_test.dart' as user_sync_test;
|
||||
|
||||
void main() async {
|
||||
appflowy_cloud_auth_test.main();
|
||||
user_sync_test.main();
|
||||
}
|
||||
+6
-6
@@ -22,12 +22,12 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../desktop/board/board_hide_groups_test.dart';
|
||||
import '../shared/database_test_op.dart';
|
||||
import '../shared/dir.dart';
|
||||
import '../shared/emoji.dart';
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
+3
-3
@@ -15,9 +15,9 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../shared/workspace.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
+5
-5
@@ -23,11 +23,11 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/shared/share/constants.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_menu.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Share menu:', () {
|
||||
testWidgets('share tab', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const pageName = 'Document';
|
||||
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// click the share button
|
||||
await tester.tapShareButton();
|
||||
|
||||
// expect the share menu is shown
|
||||
final shareMenu = find.byType(ShareMenu);
|
||||
expect(shareMenu, findsOneWidget);
|
||||
|
||||
// click the copy link button
|
||||
final copyLinkButton = find.textContaining(
|
||||
LocaleKeys.button_copyLink.tr(),
|
||||
);
|
||||
await tester.tapButton(copyLinkButton);
|
||||
|
||||
// read the clipboard content
|
||||
final clipboardContent = await getIt<ClipboardService>().getData();
|
||||
final plainText = clipboardContent.plainText;
|
||||
expect(
|
||||
plainText,
|
||||
startsWith(ShareConstants.shareBaseUrl),
|
||||
);
|
||||
|
||||
final shareValues = plainText!
|
||||
.replaceAll('${ShareConstants.shareBaseUrl}/', '')
|
||||
.split('/');
|
||||
final workspaceId = shareValues[0];
|
||||
expect(workspaceId, isNotEmpty);
|
||||
final pageId = shareValues[1];
|
||||
expect(pageId, isNotEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('workspace icon:', () {
|
||||
testWidgets('remove icon from workspace', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
await tester.openWorkspaceMenu();
|
||||
|
||||
// click the workspace icon
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(WorkspaceMenuItem),
|
||||
matching: find.byType(WorkspaceIcon),
|
||||
),
|
||||
);
|
||||
// click the remove icon button
|
||||
await tester.tapButton(
|
||||
find.text(LocaleKeys.button_remove.tr()),
|
||||
);
|
||||
|
||||
// nothing should happen
|
||||
expect(
|
||||
find.text(LocaleKeys.workspace_updateIconSuccess.tr()),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+5
-5
@@ -27,11 +27,11 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'change_name_and_icon_test.dart' as change_name_and_icon_test;
|
||||
import 'collaborative_workspace_test.dart' as collaborative_workspace_test;
|
||||
import 'share_menu_test.dart' as share_menu_test;
|
||||
import 'workspace_icon_test.dart' as workspace_icon_test;
|
||||
import 'workspace_settings_test.dart' as workspace_settings_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
workspace_settings_test.main();
|
||||
share_menu_test.main();
|
||||
collaborative_workspace_test.main();
|
||||
change_name_and_icon_test.main();
|
||||
workspace_icon_test.main();
|
||||
}
|
||||
+68
-1
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_editor.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -277,5 +277,72 @@ void main() {
|
||||
|
||||
tester.assertRowDetailPageOpened();
|
||||
});
|
||||
|
||||
testWidgets('filter calendar events', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// Create the calendar view
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Calendar,
|
||||
);
|
||||
|
||||
// Create a new event on the first of this month
|
||||
final today = DateTime.now();
|
||||
final firstOfThisMonth = DateTime(today.year, today.month);
|
||||
await tester.doubleClickCalendarCell(firstOfThisMonth);
|
||||
await tester.dismissEventEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
await tester.openCalendarEvent(index: 0, date: firstOfThisMonth);
|
||||
await tester.tapButton(finderForFieldType(FieldType.MultiSelect));
|
||||
await tester.createOption(name: "asdf");
|
||||
await tester.createOption(name: "qwer");
|
||||
await tester.selectOption(name: "asdf");
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(FieldType.MultiSelect, "Tags");
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.tapOptionFilterWithName('asdf');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(0);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.tapOptionFilterWithName('asdf');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.tapOptionFilterWithName('asdf');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(0);
|
||||
|
||||
final secondOfThisMonth = DateTime(today.year, today.month, 2);
|
||||
await tester.doubleClickCalendarCell(secondOfThisMonth);
|
||||
await tester.dismissEventEditor();
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
await tester.openCalendarEvent(index: 0, date: secondOfThisMonth);
|
||||
await tester.tapButton(finderForFieldType(FieldType.MultiSelect));
|
||||
await tester.selectOption(name: "asdf");
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(0);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.changeSelectFilterCondition(
|
||||
SelectOptionFilterConditionPB.OptionIsEmpty,
|
||||
);
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
tester.assertNumberOfEventsOnSpecificDay(1, secondOfThisMonth);
|
||||
});
|
||||
});
|
||||
}
|
||||
+17
-17
@@ -211,21 +211,21 @@ void main() {
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
// Select a date
|
||||
final today = DateTime.now();
|
||||
await tester.selectDay(content: today.day);
|
||||
final now = DateTime.now();
|
||||
await tester.selectDay(content: now.day);
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('MMM dd, y').format(today),
|
||||
content: DateFormat('MMM dd, y').format(now),
|
||||
);
|
||||
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
|
||||
// Toggle include time
|
||||
final now = DateTime.now();
|
||||
// When toggling include time, the time value is from the previous existing date time, not the current time
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
@@ -299,7 +299,7 @@ void main() {
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Make sure the option is created and displayed in the cell
|
||||
await tester.findSelectOptionWithNameInGrid(
|
||||
tester.findSelectOptionWithNameInGrid(
|
||||
rowIndex: 0,
|
||||
name: 'tag 1',
|
||||
);
|
||||
@@ -311,12 +311,12 @@ void main() {
|
||||
await tester.createOption(name: 'tag 2');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.findSelectOptionWithNameInGrid(
|
||||
tester.findSelectOptionWithNameInGrid(
|
||||
rowIndex: 0,
|
||||
name: 'tag 2',
|
||||
);
|
||||
|
||||
await tester.assertNumberOfSelectedOptionsInGrid(
|
||||
tester.assertNumberOfSelectedOptionsInGrid(
|
||||
rowIndex: 0,
|
||||
matcher: findsOneWidget,
|
||||
);
|
||||
@@ -328,12 +328,12 @@ void main() {
|
||||
await tester.selectOption(name: 'tag 1');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.findSelectOptionWithNameInGrid(
|
||||
tester.findSelectOptionWithNameInGrid(
|
||||
rowIndex: 0,
|
||||
name: 'tag 1',
|
||||
);
|
||||
|
||||
await tester.assertNumberOfSelectedOptionsInGrid(
|
||||
tester.assertNumberOfSelectedOptionsInGrid(
|
||||
rowIndex: 0,
|
||||
matcher: findsOneWidget,
|
||||
);
|
||||
@@ -345,7 +345,7 @@ void main() {
|
||||
await tester.selectOption(name: 'tag 1');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.assertNumberOfSelectedOptionsInGrid(
|
||||
tester.assertNumberOfSelectedOptionsInGrid(
|
||||
rowIndex: 0,
|
||||
matcher: findsNothing,
|
||||
);
|
||||
@@ -378,7 +378,7 @@ void main() {
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Make sure the option is created and displayed in the cell
|
||||
await tester.findSelectOptionWithNameInGrid(
|
||||
tester.findSelectOptionWithNameInGrid(
|
||||
rowIndex: 0,
|
||||
name: tags.first,
|
||||
);
|
||||
@@ -393,13 +393,13 @@ void main() {
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
for (final tag in tags) {
|
||||
await tester.findSelectOptionWithNameInGrid(
|
||||
tester.findSelectOptionWithNameInGrid(
|
||||
rowIndex: 0,
|
||||
name: tag,
|
||||
);
|
||||
}
|
||||
|
||||
await tester.assertNumberOfSelectedOptionsInGrid(
|
||||
tester.assertNumberOfSelectedOptionsInGrid(
|
||||
rowIndex: 0,
|
||||
matcher: findsNWidgets(4),
|
||||
);
|
||||
@@ -413,7 +413,7 @@ void main() {
|
||||
}
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.assertNumberOfSelectedOptionsInGrid(
|
||||
tester.assertNumberOfSelectedOptionsInGrid(
|
||||
rowIndex: 0,
|
||||
matcher: findsNothing,
|
||||
);
|
||||
@@ -426,16 +426,16 @@ void main() {
|
||||
await tester.selectOption(name: tags[3]);
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.findSelectOptionWithNameInGrid(
|
||||
tester.findSelectOptionWithNameInGrid(
|
||||
rowIndex: 0,
|
||||
name: tags[1],
|
||||
);
|
||||
await tester.findSelectOptionWithNameInGrid(
|
||||
tester.findSelectOptionWithNameInGrid(
|
||||
rowIndex: 0,
|
||||
name: tags[3],
|
||||
);
|
||||
|
||||
await tester.assertNumberOfSelectedOptionsInGrid(
|
||||
tester.assertNumberOfSelectedOptionsInGrid(
|
||||
rowIndex: 0,
|
||||
matcher: findsNWidgets(2),
|
||||
);
|
||||
|
||||
+11
-2
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -10,7 +10,7 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('database field settings', () {
|
||||
group('grid field settings test:', () {
|
||||
testWidgets('field visibility', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -50,6 +50,15 @@ void main() {
|
||||
await tester.tapHidePropertyButtonInFieldEditor();
|
||||
await tester.dismissRowDetailPage();
|
||||
tester.noFieldWithName('New field 1');
|
||||
|
||||
// the field should still be sort and filter-able
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(
|
||||
FieldType.RichText,
|
||||
"New field 1",
|
||||
);
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.RichText, "New field 1");
|
||||
});
|
||||
});
|
||||
}
|
||||
+239
-6
@@ -1,11 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/field/type_option_editor/select/select_option.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -15,7 +16,7 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid field editor:', () {
|
||||
group('grid edit field test:', () {
|
||||
testWidgets('rename existing field', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -24,7 +25,6 @@ void main() {
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Name');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
await tester.renameField('hello world');
|
||||
await tester.dismissFieldEditor();
|
||||
@@ -33,6 +33,32 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('edit field icon', (tester) async {
|
||||
const icon = 'artificial_intelligence/ai-upscale-spark';
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
tester.assertFieldSvg('Name', FieldType.RichText);
|
||||
|
||||
// choose specific icon
|
||||
await tester.tapGridFieldWithName('Name');
|
||||
await tester.changeFieldIcon(icon);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
tester.assertFieldCustomSvg('Name', icon);
|
||||
|
||||
// remove icon
|
||||
await tester.tapGridFieldWithName('Name');
|
||||
await tester.changeFieldIcon('');
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
tester.assertFieldSvg('Name', FieldType.RichText);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('update field type of existing field', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -126,7 +152,7 @@ void main() {
|
||||
await tester.dismissFieldEditor();
|
||||
tester.findFieldWithName('Right');
|
||||
|
||||
// insert new field to the right
|
||||
// insert new field to the left
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapInsertFieldButton(left: true, name: "Left");
|
||||
await tester.dismissFieldEditor();
|
||||
@@ -245,7 +271,6 @@ void main() {
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await tester.enterText(inputField, text);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
@@ -292,6 +317,30 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('text in viewport while typing', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
await tester.changeCalculateAtIndex(0, CalculationType.Count);
|
||||
|
||||
// add very large text with 200 lines
|
||||
final largeText = List.generate(
|
||||
200,
|
||||
(index) => 'Line ${index + 1}',
|
||||
).join('\n');
|
||||
|
||||
await tester.editCell(
|
||||
rowIndex: 2,
|
||||
fieldType: FieldType.RichText,
|
||||
input: largeText,
|
||||
);
|
||||
|
||||
// checks if last line is in view port
|
||||
tester.expectToSeeText('Line 200');
|
||||
});
|
||||
|
||||
// Disable this test because it fails on CI randomly
|
||||
// testWidgets('last modified and created at field type options',
|
||||
// (tester) async {
|
||||
@@ -357,5 +406,189 @@ void main() {
|
||||
// content: DateFormat('dd/MM/y hh:mm a').format(modified),
|
||||
// );
|
||||
// });
|
||||
|
||||
testWidgets('select option transform', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Grid,
|
||||
);
|
||||
|
||||
// invoke the field editor of existing Single-Select field Type
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// add some select options
|
||||
await tester.tapAddSelectOptionButton();
|
||||
for (final optionName in ['A', 'B', 'C']) {
|
||||
final inputField = find.descendant(
|
||||
of: find.byType(CreateOptionTextField),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await tester.enterText(inputField, optionName);
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
}
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// select A in first row's cell under the Type field
|
||||
await tester.tapCellInGrid(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.SingleSelect,
|
||||
);
|
||||
await tester.selectOption(name: 'A');
|
||||
await tester.dismissCellEditor();
|
||||
tester.findSelectOptionWithNameInGrid(name: 'A', rowIndex: 0);
|
||||
|
||||
await tester.changeFieldTypeOfFieldWithName('Type', FieldType.RichText);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.RichText,
|
||||
content: "A",
|
||||
cellIndex: 1,
|
||||
);
|
||||
|
||||
// add some random text in the second row
|
||||
await tester.editCell(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.RichText,
|
||||
input: "random",
|
||||
cellIndex: 1,
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.RichText,
|
||||
content: "random",
|
||||
cellIndex: 1,
|
||||
);
|
||||
|
||||
await tester.changeFieldTypeOfFieldWithName(
|
||||
'Type',
|
||||
FieldType.SingleSelect,
|
||||
);
|
||||
tester.findSelectOptionWithNameInGrid(name: 'A', rowIndex: 0);
|
||||
tester.assertNumberOfSelectedOptionsInGrid(
|
||||
rowIndex: 1,
|
||||
matcher: findsNothing,
|
||||
);
|
||||
|
||||
// create a new field for testing
|
||||
await tester.createField(FieldType.RichText, name: 'Test');
|
||||
|
||||
// edit the first 2 rows
|
||||
await tester.editCell(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.RichText,
|
||||
input: "E,F",
|
||||
cellIndex: 1,
|
||||
);
|
||||
await tester.editCell(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.RichText,
|
||||
input: "G",
|
||||
cellIndex: 1,
|
||||
);
|
||||
|
||||
await tester.changeFieldTypeOfFieldWithName(
|
||||
'Test',
|
||||
FieldType.MultiSelect,
|
||||
);
|
||||
tester.assertMultiSelectOption(contents: ['E', 'F'], rowIndex: 0);
|
||||
tester.assertMultiSelectOption(contents: ['G'], rowIndex: 1);
|
||||
|
||||
await tester.tapCellInGrid(
|
||||
rowIndex: 2,
|
||||
fieldType: FieldType.MultiSelect,
|
||||
);
|
||||
await tester.selectOption(name: 'G');
|
||||
await tester.createOption(name: 'H');
|
||||
await tester.dismissCellEditor();
|
||||
tester.findSelectOptionWithNameInGrid(name: 'A', rowIndex: 0);
|
||||
tester.assertMultiSelectOption(contents: ['G', 'H'], rowIndex: 2);
|
||||
|
||||
await tester.changeFieldTypeOfFieldWithName(
|
||||
'Test',
|
||||
FieldType.RichText,
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 2,
|
||||
fieldType: FieldType.RichText,
|
||||
content: "G,H",
|
||||
cellIndex: 1,
|
||||
);
|
||||
await tester.changeFieldTypeOfFieldWithName(
|
||||
'Test',
|
||||
FieldType.MultiSelect,
|
||||
);
|
||||
|
||||
tester.assertMultiSelectOption(contents: ['E', 'F'], rowIndex: 0);
|
||||
tester.assertMultiSelectOption(contents: ['G'], rowIndex: 1);
|
||||
tester.assertMultiSelectOption(contents: ['G', 'H'], rowIndex: 2);
|
||||
});
|
||||
|
||||
testWidgets('date time transform', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
|
||||
// create a date field
|
||||
await tester.createField(FieldType.DateTime);
|
||||
|
||||
// edit the first date cell
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.toggleIncludeTime();
|
||||
final now = DateTime.now();
|
||||
await tester.selectDay(content: now.day);
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('MMM dd, y HH:mm').format(now),
|
||||
);
|
||||
|
||||
await tester.changeFieldTypeOfFieldWithName(
|
||||
'Date',
|
||||
FieldType.RichText,
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.RichText,
|
||||
content: DateFormat('MMM dd, y HH:mm').format(now),
|
||||
cellIndex: 1,
|
||||
);
|
||||
|
||||
await tester.editCell(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.RichText,
|
||||
input: "Oct 5, 2024",
|
||||
cellIndex: 1,
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.RichText,
|
||||
content: "Oct 5, 2024",
|
||||
cellIndex: 1,
|
||||
);
|
||||
|
||||
await tester.changeFieldTypeOfFieldWithName(
|
||||
'Date',
|
||||
FieldType.DateTime,
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('MMM dd, y').format(now),
|
||||
// content: DateFormat('MMM dd, y HH:mm').format(now),
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: "Oct 05, 2024",
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+84
-2
@@ -1,10 +1,14 @@
|
||||
import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -104,7 +108,7 @@ void main() {
|
||||
await tester.tapOptionFilterWithName('s4');
|
||||
|
||||
// The row with 's4' should be shown.
|
||||
tester.assertNumberOfRowsInGridPage(1);
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
@@ -138,5 +142,83 @@ void main() {
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('add date filter', (tester) async {
|
||||
await tester.openTestDatabase(v020GridFileName);
|
||||
|
||||
// create a filter
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(FieldType.DateTime, 'date');
|
||||
|
||||
// By default, the condition of date filter is current day and time
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
|
||||
await tester.tapFilterButtonInGrid('date');
|
||||
await tester.changeDateFilterCondition(DateTimeFilterCondition.before);
|
||||
tester.assertNumberOfRowsInGridPage(7);
|
||||
|
||||
await tester.changeDateFilterCondition(DateTimeFilterCondition.isEmpty);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('add timestamp filter', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
await tester.createField(
|
||||
FieldType.CreatedTime,
|
||||
name: 'Created at',
|
||||
);
|
||||
|
||||
// create a filter
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(
|
||||
FieldType.CreatedTime,
|
||||
'Created at',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Created at');
|
||||
await tester.changeDateFilterCondition(DateTimeFilterCondition.before);
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('create new row when filters don\'t autofill', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// create a filter
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(
|
||||
FieldType.RichText,
|
||||
'Name',
|
||||
);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.tapCreateRowButtonInGrid();
|
||||
tester.assertNumberOfRowsInGridPage(4);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Name');
|
||||
await tester
|
||||
.changeTextFilterCondition(TextFilterConditionPB.TextIsNotEmpty);
|
||||
await tester.dismissCellEditor();
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
|
||||
await tester.tapCreateRowButtonInGrid();
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
expect(find.byType(RowDetailPage), findsOneWidget);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
});
|
||||
}
|
||||
+2
-3
@@ -1,19 +1,18 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/card.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/media_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_banner.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/shared/af_image.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
+138
-1
@@ -1,3 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -9,7 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -335,5 +338,139 @@ void main() {
|
||||
|
||||
tester.assertNumberOfRowsInGridPage(4);
|
||||
});
|
||||
|
||||
testWidgets('edit checklist cell', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
const fieldType = FieldType.Checklist;
|
||||
await tester.createField(fieldType);
|
||||
|
||||
await tester.openFirstRowDetailPage();
|
||||
await tester.hoverOnWidget(
|
||||
find.byType(ChecklistRowDetailCell),
|
||||
onHover: () async {
|
||||
await tester.tapButton(find.byType(ChecklistItemControl));
|
||||
},
|
||||
);
|
||||
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 0);
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 1');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 1);
|
||||
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 2');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 2);
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(PhantomChecklistItem), findsNothing);
|
||||
|
||||
await tester.renameChecklistTask(index: 0, name: "task -1", enter: false);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task -1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 1);
|
||||
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 0');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 2);
|
||||
|
||||
await tester.checkChecklistTask(index: 1);
|
||||
expect(find.byType(PhantomChecklistItem), findsNothing);
|
||||
expect(find.byType(ChecklistItem), findsNWidgets(3));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task -1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 0",
|
||||
isChecked: true,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 2,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(ProgressAndHideCompleteButton),
|
||||
matching: find.byType(FlowyIconButton),
|
||||
),
|
||||
);
|
||||
expect(find.byType(ChecklistItem), findsNWidgets(2));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task -1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
await tester.renameChecklistTask(index: 1, name: "task 3", enter: false);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
await tester.renameChecklistTask(index: 0, name: "task 1", enter: false);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 2');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 2,
|
||||
name: "task 3",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 2);
|
||||
|
||||
await tester.checkChecklistTask(index: 1);
|
||||
expect(find.byType(ChecklistItem), findsNWidgets(2));
|
||||
});
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -115,7 +115,7 @@ void main() {
|
||||
[],
|
||||
];
|
||||
for (final (index, contents) in multiSelectCells.indexed) {
|
||||
await tester.assertMultiSelectOption(
|
||||
tester.assertMultiSelectOption(
|
||||
rowIndex: index,
|
||||
contents: contents,
|
||||
);
|
||||
|
||||
+109
-12
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/sort_editor.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -7,8 +8,8 @@ import '../../shared/database_test_op.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid', () {
|
||||
testWidgets('add text sort', (tester) async {
|
||||
group('grid sort:', () {
|
||||
testWidgets('text sort', (tester) async {
|
||||
await tester.openTestDatabase(v020GridFileName);
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
@@ -37,7 +38,7 @@ void main() {
|
||||
|
||||
// open the sort menu and select order by descending
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapSortButtonByName('Name');
|
||||
await tester.tapEditSortConditionButtonByFieldName('Name');
|
||||
await tester.tapSortByDescending();
|
||||
for (final (index, content) in <String>[
|
||||
'E',
|
||||
@@ -84,7 +85,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('add checkbox sort', (tester) async {
|
||||
testWidgets('checkbox', (tester) async {
|
||||
await tester.openTestDatabase(v020GridFileName);
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
@@ -111,7 +112,7 @@ void main() {
|
||||
|
||||
// open the sort menu and select order by descending
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapSortButtonByName('Done');
|
||||
await tester.tapEditSortConditionButtonByFieldName('Done');
|
||||
await tester.tapSortByDescending();
|
||||
for (final (index, content) in <bool>[
|
||||
true,
|
||||
@@ -134,7 +135,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('add number sort', (tester) async {
|
||||
testWidgets('number', (tester) async {
|
||||
await tester.openTestDatabase(v020GridFileName);
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
@@ -162,7 +163,7 @@ void main() {
|
||||
|
||||
// open the sort menu and select order by descending
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapSortButtonByName('number');
|
||||
await tester.tapEditSortConditionButtonByFieldName('number');
|
||||
await tester.tapSortByDescending();
|
||||
for (final (index, content) in <String>[
|
||||
'12',
|
||||
@@ -186,7 +187,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('add checkbox and number sort', (tester) async {
|
||||
testWidgets('checkbox and number', (tester) async {
|
||||
await tester.openTestDatabase(v020GridFileName);
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
@@ -194,7 +195,7 @@ void main() {
|
||||
|
||||
// open the sort menu and sort checkbox by descending
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapSortButtonByName('Done');
|
||||
await tester.tapEditSortConditionButtonByFieldName('Done');
|
||||
await tester.tapSortByDescending();
|
||||
for (final (index, content) in <bool>[
|
||||
true,
|
||||
@@ -220,7 +221,7 @@ void main() {
|
||||
FieldType.Number,
|
||||
'number',
|
||||
);
|
||||
await tester.tapSortButtonByName('number');
|
||||
await tester.tapEditSortConditionButtonByFieldName('number');
|
||||
await tester.tapSortByDescending();
|
||||
|
||||
// check checkbox cell order
|
||||
@@ -273,7 +274,7 @@ void main() {
|
||||
|
||||
// open the sort menu and sort checkbox by descending
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapSortButtonByName('Done');
|
||||
await tester.tapEditSortConditionButtonByFieldName('Done');
|
||||
await tester.tapSortByDescending();
|
||||
|
||||
// add another sort, this time by number descending
|
||||
@@ -282,7 +283,7 @@ void main() {
|
||||
FieldType.Number,
|
||||
'number',
|
||||
);
|
||||
await tester.tapSortButtonByName('number');
|
||||
await tester.tapEditSortConditionButtonByFieldName('number');
|
||||
await tester.tapSortByDescending();
|
||||
|
||||
// check checkbox cell order
|
||||
@@ -370,5 +371,101 @@ void main() {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('edit field', (tester) async {
|
||||
await tester.openTestDatabase(v020GridFileName);
|
||||
|
||||
// create a number sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.Number, 'number');
|
||||
|
||||
// check the number cell order
|
||||
for (final (index, content) in <String>[
|
||||
'-2',
|
||||
'-1',
|
||||
'0.1',
|
||||
'0.2',
|
||||
'1',
|
||||
'2',
|
||||
'10',
|
||||
'11',
|
||||
'12',
|
||||
'',
|
||||
].indexed) {
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.Number,
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
|
||||
final textCells = <String>[
|
||||
'B',
|
||||
'A',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
];
|
||||
for (final (index, content) in textCells.indexed) {
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.RichText,
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
|
||||
// edit the name of the number field
|
||||
await tester.tapGridFieldWithName('number');
|
||||
|
||||
await tester.renameField('hello world');
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
await tester.tapGridFieldWithName('hello world');
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// expect name to be changed as well
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
final sortItem = find.ancestor(
|
||||
of: find.text('hello world'),
|
||||
matching: find.byType(DatabaseSortItem),
|
||||
);
|
||||
expect(sortItem, findsOneWidget);
|
||||
|
||||
// change the field type of the field to checkbox
|
||||
await tester.tapGridFieldWithName('hello world');
|
||||
await tester.changeFieldTypeOfFieldWithName(
|
||||
'hello world',
|
||||
FieldType.Checkbox,
|
||||
);
|
||||
|
||||
// expect name to be changed as well
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
expect(sortItem, findsOneWidget);
|
||||
|
||||
final newTextCells = <String>[
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
];
|
||||
for (final (index, content) in newTextCells.indexed) {
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.RichText,
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Editor AppLifeCycle tests', () {
|
||||
testWidgets(
|
||||
'Selection is added back after pausing AppFlowy',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
final selection = Selection.single(path: [4], startOffset: 0);
|
||||
await tester.editor.updateSelection(selection);
|
||||
|
||||
binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
|
||||
expect(tester.editor.getCurrentEditorState().selection, null);
|
||||
|
||||
binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.editor.getCurrentEditorState().selection, selection);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Null selection is retained after pausing AppFlowy',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
final selection = Selection.single(path: [4], startOffset: 0);
|
||||
await tester.editor.updateSelection(selection);
|
||||
await tester.editor.updateSelection(null);
|
||||
|
||||
binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
|
||||
expect(tester.editor.getCurrentEditorState().selection, null);
|
||||
|
||||
binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.editor.getCurrentEditorState().selection, null);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Non-collapsed selection is retained after pausing AppFlowy',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
final selection = Selection(
|
||||
start: Position(path: [3]),
|
||||
end: Position(path: [3], offset: 8),
|
||||
);
|
||||
await tester.editor.updateSelection(selection);
|
||||
|
||||
binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
|
||||
binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.editor.getCurrentEditorState().selection, selection);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
+4
-2
@@ -13,13 +13,15 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('paste in codeblock', () {
|
||||
group('paste in codeblock:', () {
|
||||
testWidgets('paste multiple lines in codeblock', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'Test Document');
|
||||
// focus on the editor
|
||||
await tester.tapButton(find.byType(AppFlowyEditor));
|
||||
|
||||
// mock the clipboard
|
||||
const lines = 3;
|
||||
|
||||
+160
-12
@@ -1,12 +1,19 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
@@ -226,15 +233,14 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('paste image(gif) from memory', (tester) async {
|
||||
// It's not supported yet.
|
||||
// final image = await rootBundle.load('assets/test/images/sample.gif');
|
||||
// final bytes = image.buffer.asUint8List();
|
||||
// await tester.pasteContent(image: ('gif', bytes), (editorState) {
|
||||
// expect(editorState.document.root.children.length, 2);
|
||||
// final node = editorState.getNodeAtPath([0])!;
|
||||
// expect(node.type, ImageBlockKeys.type);
|
||||
// expect(node.attributes[ImageBlockKeys.url], isNotNull);
|
||||
// });
|
||||
final image = await rootBundle.load('assets/test/images/sample.gif');
|
||||
final bytes = image.buffer.asUint8List();
|
||||
await tester.pasteContent(image: ('gif', bytes), (editorState) {
|
||||
expect(editorState.document.root.children.length, 1);
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ImageBlockKeys.type);
|
||||
expect(node.attributes[ImageBlockKeys.url], isNotNull);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
@@ -312,15 +318,155 @@ void main() {
|
||||
'auto convert url to link preview block',
|
||||
(tester) async {
|
||||
const url = 'https://appflowy.io';
|
||||
await tester.pasteContent(plainText: url, (editorState) {
|
||||
await tester.pasteContent(plainText: url, (editorState) async {
|
||||
// the second one is the paragraph node
|
||||
expect(editorState.document.root.children.length, 2);
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, LinkPreviewBlockKeys.type);
|
||||
expect(node.attributes[LinkPreviewBlockKeys.url], url);
|
||||
});
|
||||
|
||||
// hover on the link preview block
|
||||
// click the more button
|
||||
// and select convert to link
|
||||
await tester.hoverOnWidget(
|
||||
find.byType(CustomLinkPreviewWidget),
|
||||
onHover: () async {
|
||||
final convertToLinkButton = find.byWidgetPredicate((widget) {
|
||||
return widget is MenuBlockButton &&
|
||||
widget.tooltip ==
|
||||
LocaleKeys.document_plugins_urlPreview_convertToLink.tr();
|
||||
});
|
||||
expect(convertToLinkButton, findsOneWidget);
|
||||
await tester.tap(convertToLinkButton);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final textNode = editorState.getNodeAtPath([0])!;
|
||||
expect(textNode.type, ParagraphBlockKeys.type);
|
||||
expect(textNode.delta!.toJson(), [
|
||||
{
|
||||
'insert': url,
|
||||
'attributes': {'href': url},
|
||||
}
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'ctrl/cmd+z to undo the auto convert url to link preview block',
|
||||
(tester) async {
|
||||
const url = 'https://appflowy.io';
|
||||
await tester.pasteContent(plainText: url, (editorState) async {
|
||||
// the second one is the paragraph node
|
||||
expect(editorState.document.root.children.length, 2);
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, LinkPreviewBlockKeys.type);
|
||||
expect(node.attributes[LinkPreviewBlockKeys.url], url);
|
||||
});
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed:
|
||||
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ParagraphBlockKeys.type);
|
||||
expect(node.delta!.toJson(), [
|
||||
{
|
||||
'insert': url,
|
||||
'attributes': {'href': url},
|
||||
}
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'paste the nodes start with non-delta node',
|
||||
(tester) async {
|
||||
await tester.pasteContent((_) {});
|
||||
const text = 'Hello World';
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final transaction = editorState.transaction;
|
||||
// [image_block]
|
||||
// [paragraph_block]
|
||||
transaction.insertNodes([
|
||||
0,
|
||||
], [
|
||||
customImageNode(url: ''),
|
||||
paragraphNode(text: text),
|
||||
]);
|
||||
await editorState.apply(transaction);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// select all and copy
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isControlPressed:
|
||||
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyC,
|
||||
isControlPressed:
|
||||
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
|
||||
// put the cursor to the end of the paragraph block
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
// paste the content
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed:
|
||||
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect the image and the paragraph block are inserted below the cursor
|
||||
expect(editorState.getNodeAtPath([0])!.type, CustomImageBlockKeys.type);
|
||||
expect(editorState.getNodeAtPath([1])!.type, ParagraphBlockKeys.type);
|
||||
expect(editorState.getNodeAtPath([2])!.type, CustomImageBlockKeys.type);
|
||||
expect(editorState.getNodeAtPath([3])!.type, ParagraphBlockKeys.type);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('paste the url without protocol', (tester) async {
|
||||
// paste the image that from local file
|
||||
const plainText = '1.jpg';
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final bytes = image.buffer.asUint8List();
|
||||
await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes),
|
||||
(editorState) {
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ImageBlockKeys.type);
|
||||
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('paste the image url', (tester) async {
|
||||
const plainText = 'https://appflowy.io/1.jpg';
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final bytes = image.buffer.asUint8List();
|
||||
await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes),
|
||||
(editorState) {
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ImageBlockKeys.type);
|
||||
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension on WidgetTester {
|
||||
@@ -336,7 +482,9 @@ extension on WidgetTester {
|
||||
await tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await createNewPageWithNameUnderParent();
|
||||
await createNewPageWithNameUnderParent(name: 'Test Document');
|
||||
// tap the editor
|
||||
await tapButton(find.byType(AppFlowyEditor));
|
||||
|
||||
await beforeTest?.call(editor.getCurrentEditorState());
|
||||
|
||||
|
||||
+3
-6
@@ -1,6 +1,4 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -17,12 +15,11 @@ void main() {
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
const pageName = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
|
||||
// expect to see a new document
|
||||
tester.expectToSeePageName(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
);
|
||||
tester.expectToSeePageName(pageName);
|
||||
// and with one paragraph block
|
||||
expect(find.byType(ParagraphBlockComponentWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('customer:', () {
|
||||
testWidgets('backtick issue - inline code', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const pageName = 'backtick issue';
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.tap(find.byType(AppFlowyEditor));
|
||||
// input backtick
|
||||
const text = '`Hello` AppFlowy';
|
||||
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await tester.ime.insertCharacter(text[i]);
|
||||
}
|
||||
|
||||
final node = tester.editor.getNodeAtPath([0]);
|
||||
expect(
|
||||
node.delta?.toJson(),
|
||||
equals([
|
||||
{
|
||||
"insert": "Hello",
|
||||
"attributes": {"code": true},
|
||||
},
|
||||
{"insert": " AppFlowy"},
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('backtick issue - inline code', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const pageName = 'backtick issue';
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.tap(find.byType(AppFlowyEditor));
|
||||
// input backtick
|
||||
const text = '```';
|
||||
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await tester.ime.insertCharacter(text[i]);
|
||||
}
|
||||
|
||||
final node = tester.editor.getNodeAtPath([0]);
|
||||
expect(node.type, equals(CodeBlockKeys.type));
|
||||
});
|
||||
});
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
import 'document_inline_page_reference_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Document deletion', () {
|
||||
testWidgets('Trash breadcrumb', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// This test shares behavior with the inline page reference test, thus
|
||||
// we utilize the same helper functions there.
|
||||
final name = await createDocumentToReference(tester);
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await triggerReferenceDocumentBySlashMenu(tester);
|
||||
|
||||
// Search for prefix of document
|
||||
await enterDocumentText(tester);
|
||||
|
||||
// Select result
|
||||
final optionFinder = find.descendant(
|
||||
of: find.byType(InlineActionsHandler),
|
||||
matching: find.text(name),
|
||||
);
|
||||
|
||||
await tester.tap(optionFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final mentionBlock = find.byType(MentionPageBlock);
|
||||
expect(mentionBlock, findsOneWidget);
|
||||
|
||||
// Delete the page
|
||||
await tester.hoverOnPageName(
|
||||
name,
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Navigate to the deleted page from the inline mention
|
||||
await tester.tap(mentionBlock);
|
||||
await tester.pumpUntilFound(find.byType(TrashBreadcrumb));
|
||||
|
||||
expect(find.byType(TrashBreadcrumb), findsOneWidget);
|
||||
|
||||
// Navigate using the trash breadcrumb
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(TrashBreadcrumb),
|
||||
matching: find.text(
|
||||
LocaleKeys.trash_text.tr(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.text(LocaleKeys.trash_restoreAll.tr()));
|
||||
|
||||
// Restore all
|
||||
await tester.tap(find.text(LocaleKeys.trash_restoreAll.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(LocaleKeys.trash_restore.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Navigate back to the document
|
||||
await tester.openPage('Getting started');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(mentionBlock);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(TrashBreadcrumb), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/keyboard.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
const _firstDocName = "Inline Sub Page Mention";
|
||||
const _createdPageName = "hi world";
|
||||
|
||||
// Test cases that are covered in this file:
|
||||
// - [x] Insert sub page mention from action menu (+)
|
||||
// - [x] Delete sub page mention from editor
|
||||
// - [x] Delete page from sidebar
|
||||
// - [x] Delete page from sidebar and then trash
|
||||
// - [x] Undo delete sub page mention
|
||||
// - [x] Cut+paste in same document
|
||||
// - [x] Cut+paste in different document
|
||||
// - [x] Cut+paste in same document and then paste again in same document
|
||||
// - [x] Turn paragraph with sub page mention into a heading
|
||||
// - [x] Turn heading with sub page mention into a paragraph
|
||||
// - [x] Duplicate a Block containing two sub page mentions
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document inline sub-page mention tests:', () {
|
||||
testWidgets('Insert (& delete) a sub page mention from action menu',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
|
||||
// Delete from editor
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
// Undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Move to trash (delete from sidebar)
|
||||
await tester.rightClickOnPageName(_createdPageName);
|
||||
await tester.tapButtonWithName(ViewMoreActionType.delete.name);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(
|
||||
find.text(LocaleKeys.document_mention_trashHint.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// Delete from trash
|
||||
await tester.tapTrashButton();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text(LocaleKeys.trash_deleteAll.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text(LocaleKeys.button_delete.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.openPage(_firstDocName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(
|
||||
find.text(LocaleKeys.document_mention_deletedPage.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Cut+paste in same document and cut+paste in different document',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
|
||||
// Cut from editor
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
// Paste in same document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
|
||||
// Cut again
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Create another document
|
||||
const anotherDocName = "Another Document";
|
||||
await tester.createOpenRenameDocumentUnderParent(
|
||||
name: anotherDocName,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Paste in document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(MentionSubPageBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: anotherDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
});
|
||||
testWidgets(
|
||||
'Cut+paste in same docuemnt and then paste again in same document',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
|
||||
// Cut from editor
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
// Paste in same document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
|
||||
// Paste again
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsNWidgets(2));
|
||||
expect(find.text('$_createdPageName (copy)'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Turn into w/ sub page mentions', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
|
||||
final headingText = LocaleKeys.document_slashMenu_name_heading1.tr();
|
||||
final paragraphText = LocaleKeys.document_slashMenu_name_text.tr();
|
||||
|
||||
// Turn into heading
|
||||
await tester.editor.openTurnIntoMenu([0]);
|
||||
await tester.tapButton(find.findTextInFlowyText(headingText));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
|
||||
// Turn into paragraph
|
||||
await tester.editor.openTurnIntoMenu([0]);
|
||||
await tester.tapButton(find.findTextInFlowyText(paragraphText));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Duplicate a block containing two sub page mentions',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
// Copy paste it
|
||||
await tester.editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [0], offset: 1),
|
||||
),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyC,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
expect(find.text("$_createdPageName (copy)"), findsOneWidget);
|
||||
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
|
||||
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsNWidgets(2));
|
||||
|
||||
// Duplicate node from block action menu
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_duplicate.tr());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
expect(find.text("$_createdPageName (copy)"), findsNWidgets(2));
|
||||
expect(find.text("$_createdPageName (copy) (copy)"), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension _InlineSubPageTestHelper on WidgetTester {
|
||||
Future<void> insertInlineSubPageFromPlusMenu() async {
|
||||
await editor.tapLineOfEditorAt(0);
|
||||
|
||||
await editor.showPlusMenu();
|
||||
|
||||
// Workaround to allow typing a document name
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
tester: this,
|
||||
withKeyUp: true,
|
||||
[
|
||||
LogicalKeyboardKey.keyH,
|
||||
LogicalKeyboardKey.keyI,
|
||||
LogicalKeyboardKey.space,
|
||||
LogicalKeyboardKey.keyW,
|
||||
LogicalKeyboardKey.keyO,
|
||||
LogicalKeyboardKey.keyR,
|
||||
LogicalKeyboardKey.keyL,
|
||||
LogicalKeyboardKey.keyD,
|
||||
],
|
||||
);
|
||||
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
tester: this,
|
||||
withKeyUp: true,
|
||||
[LogicalKeyboardKey.enter],
|
||||
);
|
||||
await pumpUntilFound(find.byType(MentionSubPageBlock));
|
||||
}
|
||||
}
|
||||
+139
-3
@@ -1,3 +1,8 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -7,9 +12,23 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// +, ... button beside the block component.
|
||||
group('document with option action button', () {
|
||||
testWidgets(
|
||||
'click + to add a block after current selection, and click + and option key to add a block before current selection',
|
||||
group('block option action:', () {
|
||||
Future<void> turnIntoBlock(
|
||||
WidgetTester tester,
|
||||
Path path, {
|
||||
required String menuText,
|
||||
required String afterType,
|
||||
}) async {
|
||||
await tester.editor.openTurnIntoMenu(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(menuText),
|
||||
);
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath(path);
|
||||
expect(node?.type, afterType);
|
||||
}
|
||||
|
||||
testWidgets('''click + to add a block after current selection,
|
||||
and click + and option key to add a block before current selection''',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -40,5 +59,122 @@ void main() {
|
||||
expect(editorState.getNodeAtPath([0])?.delta?.toPlainText(), isEmpty);
|
||||
expect(editorState.getNodeAtPath([2])?.delta?.toPlainText(), isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('turn into - single line', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const name = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: name);
|
||||
await tester.openPage(name);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('turn into');
|
||||
|
||||
// click the block option button to convert it to another blocks
|
||||
final values = {
|
||||
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_bulletedList.tr():
|
||||
BulletedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_numberedList.tr():
|
||||
NumberedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_todoList.tr():
|
||||
TodoListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
|
||||
};
|
||||
|
||||
for (final value in values.entries) {
|
||||
final menuText = value.key;
|
||||
final afterType = value.value;
|
||||
await turnIntoBlock(
|
||||
tester,
|
||||
[0],
|
||||
menuText: menuText,
|
||||
afterType: afterType,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('turn into - multi lines', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const name = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: name);
|
||||
await tester.openPage(name);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('turn into 1');
|
||||
await tester.ime.insertCharacter('\n');
|
||||
await tester.ime.insertText('turn into 2');
|
||||
|
||||
// click the block option button to convert it to another blocks
|
||||
final values = {
|
||||
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_bulletedList.tr():
|
||||
BulletedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_numberedList.tr():
|
||||
NumberedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_todoList.tr():
|
||||
TodoListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
|
||||
};
|
||||
|
||||
for (final value in values.entries) {
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
editorState.selection = Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [1], offset: 2),
|
||||
);
|
||||
final menuText = value.key;
|
||||
final afterType = value.value;
|
||||
await turnIntoBlock(
|
||||
tester,
|
||||
[0],
|
||||
menuText: menuText,
|
||||
afterType: afterType,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'selecting the parent should deselect all the child nodes as well',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const name = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: name);
|
||||
await tester.openPage(name);
|
||||
|
||||
// create a nested list
|
||||
// Item 1
|
||||
// Nested Item 1
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('Item 1');
|
||||
await tester.ime.insertCharacter('\n');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.ime.insertText('Nested Item 1');
|
||||
|
||||
// select the 'Nested Item 1' and then tap the option button of the 'Item 1'
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final selection = Selection.collapsed(
|
||||
Position(path: [0, 0], offset: 1),
|
||||
);
|
||||
editorState.selection = selection;
|
||||
await tester.pumpAndSettle();
|
||||
expect(editorState.selection, selection);
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
expect(editorState.selection, Selection.collapsed(Position(path: [0])));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document selection:', () {
|
||||
testWidgets('select text from start to end by pan gesture ',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final editor = tester.editor;
|
||||
final editorState = editor.getCurrentEditorState();
|
||||
// insert a paragraph
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNode(
|
||||
[0],
|
||||
paragraphNode(
|
||||
text:
|
||||
'''Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.''',
|
||||
),
|
||||
);
|
||||
await editorState.apply(transaction);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
final textBlocks = find.byType(AppFlowyRichText);
|
||||
final topLeft = tester.getTopLeft(textBlocks.at(0));
|
||||
|
||||
final gesture = await tester.startGesture(
|
||||
topLeft,
|
||||
pointer: 7,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
await gesture.moveBy(const Offset(10, 0));
|
||||
await tester.pump(Durations.short1);
|
||||
}
|
||||
|
||||
expect(editorState.selection!.start.offset, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document shortcuts:', () {
|
||||
testWidgets('custom cut command', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const pageName = 'Test Document Shortcuts';
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.tap(find.byType(AppFlowyEditor));
|
||||
|
||||
// mock the data
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final transaction = editorState.transaction;
|
||||
const text1 = '1. First line';
|
||||
const text2 = '2. Second line';
|
||||
transaction.insertNodes([
|
||||
0,
|
||||
], [
|
||||
paragraphNode(text: text1),
|
||||
paragraphNode(text: text2),
|
||||
]);
|
||||
await editorState.apply(transaction);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// focus on the end of the first line
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(
|
||||
Position(path: [0], offset: text1.length),
|
||||
),
|
||||
);
|
||||
// press the keybinding
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the clipboard
|
||||
final clipboard = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
clipboard?.text,
|
||||
equals(text1),
|
||||
);
|
||||
|
||||
final node = tester.editor.getNodeAtPath([0]);
|
||||
expect(
|
||||
node.delta?.toPlainText(),
|
||||
equals(text2),
|
||||
);
|
||||
|
||||
// select the whole line
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: text2.length,
|
||||
),
|
||||
);
|
||||
|
||||
// press the keybinding
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// all the text should be deleted
|
||||
expect(
|
||||
node.delta?.toPlainText(),
|
||||
equals(''),
|
||||
);
|
||||
|
||||
final clipboard2 = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
clipboard2?.text,
|
||||
equals(text2),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'custom copy command - copy whole line when selection is collapsed',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const pageName = 'Test Document Shortcuts';
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.tap(find.byType(AppFlowyEditor));
|
||||
|
||||
// mock the data
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final transaction = editorState.transaction;
|
||||
const text1 = '1. First line';
|
||||
transaction.insertNodes([
|
||||
0,
|
||||
], [
|
||||
paragraphNode(text: text1),
|
||||
]);
|
||||
await editorState.apply(transaction);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// focus on the end of the first line
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(
|
||||
Position(path: [0], offset: text1.length),
|
||||
),
|
||||
);
|
||||
// press the keybinding
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyC,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the clipboard
|
||||
final clipboard = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
clipboard?.text,
|
||||
equals(text1),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+501
@@ -0,0 +1,501 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
// Test cases for the Document SubPageBlock that needs to be covered:
|
||||
// - [x] Insert a new SubPageBlock from Slash menu items (Expect it will create a child view under current view)
|
||||
// - [x] Delete a SubPageBlock from Block Action Menu (Expect the view is moved to trash / deleted)
|
||||
// - [x] Delete a SubPageBlock with backspace when selected (Expect the view is moved to trash / deleted)
|
||||
// - [x] Copy+paste a SubPageBlock in same Document (Expect a new view is created under current view with same content and name)
|
||||
// - [x] Copy+paste a SubPageBlock in different Document (Expect a new view is created under current view with same content and name)
|
||||
// - [x] Cut+paste a SubPageBlock in same Document (Expect the view to be deleted on Cut, and brought back on Paste)
|
||||
// - [x] Cut+paste a SubPageBlock in different Document (Expect the view to be deleted on Cut, and brought back on Paste)
|
||||
// - [x] Undo adding a SubPageBlock (Expect the view to be deleted)
|
||||
// - [x] Undo delete of a SubPageBlock (Expect the view to be brought back to original position)
|
||||
// - [x] Redo adding a SubPageBlock (Expect the view to be restored)
|
||||
// - [x] Redo delete of a SubPageBlock (Expect the view to be moved to trash again)
|
||||
// - [x] Renaming a child view (Expect the view name to be updated in the document)
|
||||
// - [x] Deleting a view (to trash) linked to a SubPageBlock deleted the SubPageBlock (Expect the SubPageBlock to be deleted)
|
||||
// - [x] Duplicating a SubPageBlock node from Action Menu (Expect a new view is created under current view with same content and name + (copy))
|
||||
// - [x] Dragging a SubPageBlock node to a new position in the document (Expect everything to be normal)
|
||||
|
||||
/// The defaut page name is empty, if we're looking for a "text" we can look for
|
||||
/// [LocaleKeys.menuAppHeader_defaultNewPageName] but it won't work for eg. hoverOnPageName
|
||||
/// as it looks at the text provided instead of the actual displayed text.
|
||||
///
|
||||
const _defaultPageName = "";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Document SubPageBlock tests', () {
|
||||
testWidgets('Insert a new SubPageBlock from Slash menu items',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.text(LocaleKeys.menuAppHeader_defaultNewPageName.tr()),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Rename and then Delete a SubPageBlock from Block Action Menu',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Copy+paste a SubPageBlock in same Document', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionAddButton([0], false);
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
|
||||
// This is a workaround to allow CTRL+A and CTRL+C to work to copy
|
||||
// the SubPageBlock as well.
|
||||
await tester.ime.insertText('ABC');
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyC,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.editor.hoverAndClickOptionAddButton([1], false);
|
||||
await tester.editor.tapLineOfEditorAt(2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 5));
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsNWidgets(2));
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
expect(find.text('Child page (copy)'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Copy+paste a SubPageBlock in different Document',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionAddButton([0], false);
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
|
||||
// This is a workaround to allow CTRL+A and CTRL+C to work to copy
|
||||
// the SubPageBlock as well.
|
||||
await tester.ime.insertText('ABC');
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyC,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock-2');
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock-2',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
expect(find.text('Child page'), findsOneWidget);
|
||||
expect(find.text('Child page (copy)'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Cut+paste a SubPageBlock in same Document', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor
|
||||
.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Cut+paste a SubPageBlock in different Document',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor
|
||||
.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock-2');
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock-2',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
expect(find.text('Child page (copy)'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Undo delete of a SubPageBlock', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
});
|
||||
|
||||
// Redo: undoing deleting a subpage block, then redoing to delete it again
|
||||
// -> Add a subpage block
|
||||
// -> Delete
|
||||
// -> Undo
|
||||
// -> Redo
|
||||
testWidgets('Redo delete of a SubPageBlock', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu(true);
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
// Delete
|
||||
await tester.editor.hoverAndClickOptionMenuButton([1]);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
// Undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
// Redo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isShiftPressed: true,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Delete a view from sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
|
||||
await tester.hoverOnPageName('Child page');
|
||||
await tester.tapDeletePageButton();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Duplicate SubPageBlock from Block Menu', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
await tester.tapButtonWithName(LocaleKeys.button_duplicate.tr());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
expect(find.text('Child page (copy)'), findsNWidgets(2));
|
||||
expect(find.byType(SubPageBlockComponent), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Drag SubPageBlock to top of Document', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu(true);
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
|
||||
final beforeNode = tester.editor.getNodeAtPath([1]);
|
||||
|
||||
await tester.editor.dragBlock([1], const Offset(20, -45));
|
||||
await tester.pumpAndSettle(Durations.long1);
|
||||
|
||||
final afterNode = tester.editor.getNodeAtPath([0]);
|
||||
|
||||
expect(afterNode.type, SubPageBlockKeys.type);
|
||||
expect(afterNode.type, beforeNode.type);
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension _SubPageTestHelper on WidgetTester {
|
||||
Future<void> insertSubPageFromSlashMenu([bool withTextNode = false]) async {
|
||||
await editor.tapLineOfEditorAt(0);
|
||||
|
||||
if (withTextNode) {
|
||||
await ime.insertText('ABC');
|
||||
await editor.getCurrentEditorState().insertNewLine();
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
await editor.showSlashMenu();
|
||||
await editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_subPage_name.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
|
||||
// Navigate to the previous page to see the SubPageBlock
|
||||
await openPage('SubPageBlock');
|
||||
await pumpAndSettle();
|
||||
|
||||
await pumpUntilFound(find.byType(SubPageBlockComponent));
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -5,10 +5,10 @@ import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
|
||||
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
||||
import 'document_create_and_delete_test.dart'
|
||||
as document_create_and_delete_test;
|
||||
import 'document_option_action_test.dart' as document_option_action_test;
|
||||
import 'document_inline_page_reference_test.dart'
|
||||
as document_inline_page_reference_test;
|
||||
import 'document_more_actions_test.dart' as document_more_actions_test;
|
||||
import 'document_shortcuts_test.dart' as document_shortcuts_test;
|
||||
import 'document_text_direction_test.dart' as document_text_direction_test;
|
||||
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
||||
import 'document_with_database_test.dart' as document_with_database_test;
|
||||
@@ -23,7 +23,7 @@ import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||
import 'edit_document_test.dart' as document_edit_test;
|
||||
|
||||
void startTesting() {
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
@@ -39,10 +39,11 @@ void startTesting() {
|
||||
document_codeblock_paste_test.main();
|
||||
document_alignment_test.main();
|
||||
document_text_direction_test.main();
|
||||
document_option_action_test.main();
|
||||
document_with_image_block_test.main();
|
||||
document_with_multi_image_block_test.main();
|
||||
document_inline_page_reference_test.main();
|
||||
document_more_actions_test.main();
|
||||
document_with_file_test.main();
|
||||
document_shortcuts_test.main();
|
||||
// Don't add new tests here. Add them to document_test_runner_2.dart
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_app_lifecycle_test.dart' as document_app_lifecycle_test;
|
||||
import 'document_deletion_test.dart' as document_deletion_test;
|
||||
import 'document_option_action_test.dart' as document_option_action_test;
|
||||
import 'document_title_test.dart' as document_title_test;
|
||||
import 'document_with_date_reminder_test.dart'
|
||||
as document_with_date_reminder_test;
|
||||
import 'document_inline_sub_page_test.dart' as document_inline_sub_page_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
document_title_test.main();
|
||||
// Disable subPage test temporarily, enable it in version 0.7.2
|
||||
// document_sub_page_test.main();
|
||||
document_app_lifecycle_test.main();
|
||||
document_with_date_reminder_test.main();
|
||||
document_deletion_test.main();
|
||||
document_option_action_test.main();
|
||||
document_inline_sub_page_test.main();
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../shared/constants.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
const _testDocumentName = 'Test Document';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document title:', () {
|
||||
testWidgets('create a new document and edit title', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
expect(title, findsOneWidget);
|
||||
|
||||
// input name
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final newTitle = tester.editor.findDocumentTitle(_testDocumentName);
|
||||
expect(newTitle, findsOneWidget);
|
||||
|
||||
// press enter to create a new line
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
const firstLine = 'First line of text';
|
||||
await tester.ime.insertText(firstLine);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final firstLineText = find.text(firstLine, findRichText: true);
|
||||
expect(firstLineText, findsOneWidget);
|
||||
|
||||
// press cmd/ctrl+left to move the cursor to the start of the line
|
||||
if (UniversalPlatform.isMacOS) {
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.arrowLeft,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
} else {
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.home);
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// press arrow left to delete the first line
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check if the title is on focus
|
||||
final titleOnFocus = tester.editor.findDocumentTitle(_testDocumentName);
|
||||
final titleWidget = tester.widget<TextField>(titleOnFocus);
|
||||
expect(titleWidget.focusNode?.hasFocus, isTrue);
|
||||
|
||||
// press the right arrow key to move the cursor to the first line
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||
|
||||
// check if the title is not on focus
|
||||
expect(titleWidget.focusNode?.hasFocus, isFalse);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(editorState.selection, Selection.collapsed(Position(path: [0])));
|
||||
|
||||
// press the backspace key to go to the title
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
|
||||
expect(editorState.selection, null);
|
||||
expect(titleWidget.focusNode?.hasFocus, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('check if the title is saved', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
expect(title, findsOneWidget);
|
||||
|
||||
// input name
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
if (UniversalPlatform.isLinux) {
|
||||
// wait for the name to be saved
|
||||
await tester.wait(250);
|
||||
}
|
||||
|
||||
// go to the get started page
|
||||
await tester.tapButton(
|
||||
tester.findPageName(Constants.gettingStartedPageName),
|
||||
);
|
||||
|
||||
// go back to the page
|
||||
await tester.tapButton(tester.findPageName(_testDocumentName));
|
||||
|
||||
// check if the title is saved
|
||||
final testDocumentTitle = tester.editor.findDocumentTitle(
|
||||
_testDocumentName,
|
||||
);
|
||||
expect(testDocumentTitle, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arrow up from first line moves focus to title',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.ime.insertText('First line of text');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.home);
|
||||
|
||||
// press the arrow upload
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||
|
||||
final titleWidget = tester.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
);
|
||||
expect(titleWidget.focusNode?.hasFocus, isTrue);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(editorState.selection, null);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'backspace at start of first line moves focus to title and deletes empty paragraph',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(editorState.document.root.children.length, equals(2));
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
|
||||
final titleWidget = tester.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
);
|
||||
expect(titleWidget.focusNode?.hasFocus, isTrue);
|
||||
|
||||
// at least one empty paragraph node is created
|
||||
expect(editorState.document.root.children.length, equals(1));
|
||||
});
|
||||
|
||||
testWidgets('arrow right from end of title moves focus to first line',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.ime.insertText('First line of text');
|
||||
|
||||
await tester.tapButton(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.end);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(
|
||||
editorState.selection,
|
||||
Selection.collapsed(
|
||||
Position(path: [0]),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('change the title via sidebar, check the title is updated',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
expect(title, findsOneWidget);
|
||||
|
||||
await tester.hoverOnPageName(
|
||||
'',
|
||||
onHover: () async {
|
||||
await tester.renamePage(_testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final newTitle = tester.editor.findDocumentTitle(_testDocumentName);
|
||||
expect(newTitle, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('execute undo and redo in title', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
// press a random key to make the undo stack not empty
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.keyA);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
// wait for the undo to be applied
|
||||
await tester.pumpAndSettle(Durations.long1);
|
||||
|
||||
// expect the title is empty
|
||||
expect(
|
||||
tester
|
||||
.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(''),
|
||||
)
|
||||
.controller
|
||||
?.text,
|
||||
'',
|
||||
);
|
||||
|
||||
// redo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
if (UniversalPlatform.isMacOS) {
|
||||
expect(
|
||||
tester
|
||||
.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
)
|
||||
.controller
|
||||
?.text,
|
||||
_testDocumentName,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('escape key should exit the editing mode', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
tester
|
||||
.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
)
|
||||
.focusNode
|
||||
?.hasFocus,
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('press arrow down key in title, check if the cursor flashes',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
const inputText = 'Hello World';
|
||||
await tester.ime.insertText(inputText);
|
||||
|
||||
await tester.tapButton(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(
|
||||
editorState.selection,
|
||||
Selection.collapsed(
|
||||
Position(path: [0], offset: inputText.length),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'hover on the cover title, check if the add icon & add cover button are shown',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.hoverOnWidget(
|
||||
title,
|
||||
onHover: () async {
|
||||
expect(find.byType(DocumentCoverWidget), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
});
|
||||
}
|
||||
+3
-4
@@ -1,9 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -14,7 +13,7 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('cover image', () {
|
||||
group('cover image:', () {
|
||||
testWidgets('document cover tests', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
+51
@@ -60,6 +60,57 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert multiple referenced boards', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new grid
|
||||
final id = uuid();
|
||||
final name = '${ViewLayoutPB.Board.name}_$id';
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: name,
|
||||
layout: ViewLayoutPB.Board,
|
||||
openAfterCreated: false,
|
||||
);
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'insert_a_reference_${ViewLayoutPB.Board.name}',
|
||||
);
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// insert a referenced view
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
ViewLayoutPB.Board.slashMenuLinkedName,
|
||||
);
|
||||
final referencedDatabase1 = find.descendant(
|
||||
of: find.byType(InlineActionsHandler),
|
||||
matching: find.findTextInFlowyText(name),
|
||||
);
|
||||
expect(referencedDatabase1, findsOneWidget);
|
||||
await tester.tapButton(referencedDatabase1);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
ViewLayoutPB.Board.slashMenuLinkedName,
|
||||
);
|
||||
final referencedDatabase2 = find.descendant(
|
||||
of: find.byType(InlineActionsHandler),
|
||||
matching: find.findTextInFlowyText(name),
|
||||
);
|
||||
expect(referencedDatabase2, findsOneWidget);
|
||||
await tester.tapButton(referencedDatabase2);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.byType(DesktopBoardPage),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert a referenced calendar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('date or reminder block in document', () {
|
||||
testWidgets("insert date with time block", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'Date with time test',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
dateFormat: UserDateFormatPB.Friendly,
|
||||
timeFormat: UserTimeFormatPB.TwentyFourHour,
|
||||
);
|
||||
final DateTime currentDateTime = DateTime.now();
|
||||
final String formattedDate =
|
||||
dateTimeSettings.dateFormat.formatDate(currentDateTime, false);
|
||||
|
||||
// get current date in editor
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
|
||||
// tap on date field
|
||||
await tester.tap(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// tap the toggle of include time
|
||||
await tester.tap(find.byType(Toggle));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// add time 11:12
|
||||
final currentTime = DateFormat('HH:mm').format(DateTime.now());
|
||||
final textField = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is TextField && widget.controller!.text == currentTime,
|
||||
);
|
||||
await tester.enterText(textField, "11:12");
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// we will get field with current date and 11:12 as time
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate 11:12'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("insert date with reminder block", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'Date with reminder test',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
dateFormat: UserDateFormatPB.Friendly,
|
||||
timeFormat: UserTimeFormatPB.TwentyFourHour,
|
||||
);
|
||||
final DateTime currentDateTime = DateTime.now();
|
||||
final String formattedDate =
|
||||
dateTimeSettings.dateFormat.formatDate(currentDateTime, false);
|
||||
|
||||
// get current date in editor
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
|
||||
// tap on date field
|
||||
await tester.tap(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// tap reminder and set reminder to 1 day before
|
||||
await tester.tap(find.text(LocaleKeys.datePicker_reminderLabel.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.textContaining(
|
||||
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// we will get field with current date reminder_clock.svg icon
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
+2
-8
@@ -1,5 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -8,7 +10,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
@@ -36,10 +37,6 @@ void main() {
|
||||
LocaleKeys.document_slashMenu_name_file.tr(),
|
||||
);
|
||||
expect(find.byType(FileBlockComponent), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(FileBlockComponent));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
expect(find.byType(FileUploadMenu), findsOneWidget);
|
||||
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
@@ -116,9 +113,6 @@ void main() {
|
||||
LocaleKeys.document_slashMenu_name_file.tr(),
|
||||
);
|
||||
expect(find.byType(FileBlockComponent), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(FileBlockComponent));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
expect(find.byType(FileUploadMenu), findsOneWidget);
|
||||
|
||||
// Navigate to integrate link tab
|
||||
|
||||
+1
-6
@@ -6,7 +6,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
@@ -160,11 +159,7 @@ void main() {
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
await tester.tapButtonWithName(
|
||||
'Unsplash',
|
||||
);
|
||||
expect(find.byType(UnsplashImageWidget), findsOneWidget);
|
||||
expect(find.text('Unsplash'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/shared/flowy_error_page.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -92,7 +92,7 @@ void main() {
|
||||
);
|
||||
expect(finder, findsOneWidget);
|
||||
await tester.tapButton(finder);
|
||||
expect(find.byType(AppFlowyErrorPage), findsOneWidget);
|
||||
expect(find.byType(GridPage), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+3
-10
@@ -1,9 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -182,13 +179,9 @@ Future<void> hoverAndClickDepthOptionAction(
|
||||
List<int> path,
|
||||
int level,
|
||||
) async {
|
||||
await tester.editor.hoverAndClickOptionMenuButton([3]);
|
||||
await tester.tap(find.byType(AppFlowyPopover).hitTestable().last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find a total of 4 HoverButtons under the [BlockOptionButton],
|
||||
// in addition to 3 HoverButtons under the [DepthOptionAction] - (child of BlockOptionButton)
|
||||
await tester.tap(find.byType(HoverButton).hitTestable().at(3 + level));
|
||||
await tester.editor.openDepthMenu(path);
|
||||
final type = OptionDepthType.fromLevel(level);
|
||||
await tester.tapButton(find.findTextInFlowyText(type.description));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:time/time.dart';
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import 'grid_test_extensions.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid edit row test:', () {
|
||||
testWidgets('with sort configured', (tester) async {
|
||||
await tester.openTestDatabase(v069GridFileName);
|
||||
|
||||
// get grid data
|
||||
final unsorted = tester.getGridRows();
|
||||
|
||||
// add a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.RichText, 'Name');
|
||||
|
||||
final sorted = [
|
||||
unsorted[7],
|
||||
unsorted[8],
|
||||
unsorted[1],
|
||||
unsorted[9],
|
||||
unsorted[11],
|
||||
unsorted[10],
|
||||
unsorted[6],
|
||||
unsorted[12],
|
||||
unsorted[2],
|
||||
unsorted[0],
|
||||
unsorted[3],
|
||||
unsorted[5],
|
||||
unsorted[4],
|
||||
];
|
||||
|
||||
List actual = tester.getGridRows();
|
||||
expect(actual, orderedEquals(sorted));
|
||||
|
||||
await tester.editCell(
|
||||
rowIndex: 4,
|
||||
fieldType: FieldType.RichText,
|
||||
input: "x",
|
||||
);
|
||||
await tester.pumpAndSettle(200.milliseconds);
|
||||
|
||||
final reSorted = [
|
||||
unsorted[7],
|
||||
unsorted[8],
|
||||
unsorted[1],
|
||||
unsorted[9],
|
||||
unsorted[10],
|
||||
unsorted[6],
|
||||
unsorted[12],
|
||||
unsorted[2],
|
||||
unsorted[0],
|
||||
unsorted[3],
|
||||
unsorted[5],
|
||||
unsorted[11],
|
||||
unsorted[4],
|
||||
];
|
||||
|
||||
// verify grid data
|
||||
actual = tester.getGridRows();
|
||||
expect(actual, orderedEquals(reSorted));
|
||||
|
||||
// delete the sort
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapDeleteAllSortsButton();
|
||||
|
||||
// verify grid data
|
||||
actual = tester.getGridRows();
|
||||
expect(actual, orderedEquals(unsorted));
|
||||
});
|
||||
|
||||
testWidgets('with filter configured', (tester) async {
|
||||
await tester.openTestDatabase(v069GridFileName);
|
||||
|
||||
// get grid data
|
||||
final original = tester.getGridRows();
|
||||
|
||||
// create a filter
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(
|
||||
FieldType.Checkbox,
|
||||
'Registration Complete',
|
||||
);
|
||||
|
||||
final filtered = [
|
||||
original[1],
|
||||
original[3],
|
||||
original[5],
|
||||
original[6],
|
||||
original[7],
|
||||
original[9],
|
||||
original[12],
|
||||
];
|
||||
|
||||
// verify grid data
|
||||
List actual = tester.getGridRows();
|
||||
expect(actual, orderedEquals(filtered));
|
||||
expect(actual.length, equals(7));
|
||||
tester.assertNumberOfRowsInGridPage(7);
|
||||
|
||||
await tester.tapCheckboxCellInGrid(rowIndex: 0);
|
||||
await tester.pumpAndSettle(200.milliseconds);
|
||||
|
||||
// verify grid data
|
||||
actual = tester.getGridRows();
|
||||
expect(actual.length, equals(6));
|
||||
tester.assertNumberOfRowsInGridPage(6);
|
||||
final edited = [
|
||||
original[3],
|
||||
original[5],
|
||||
original[6],
|
||||
original[7],
|
||||
original[9],
|
||||
original[12],
|
||||
];
|
||||
expect(actual, orderedEquals(edited));
|
||||
|
||||
// delete the filter
|
||||
await tester.tapFilterButtonInGrid('Registration Complete');
|
||||
await tester
|
||||
.tapDisclosureButtonInFinder(find.byType(CheckboxFilterEditor));
|
||||
await tester.tapDeleteFilterButtonInGrid();
|
||||
|
||||
// verify grid data
|
||||
actual = tester.getGridRows();
|
||||
expect(actual.length, equals(13));
|
||||
tester.assertNumberOfRowsInGridPage(13);
|
||||
});
|
||||
});
|
||||
}
|
||||
+43
-12
@@ -10,13 +10,14 @@ import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
import 'grid_test_extensions.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid create row test:', () {
|
||||
testWidgets('from the bottom', (tester) async {
|
||||
group('grid row test:', () {
|
||||
testWidgets('create from the bottom', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
@@ -33,7 +34,7 @@ void main() {
|
||||
tester.assertNumberOfRowsInGridPage(4);
|
||||
});
|
||||
|
||||
testWidgets('from a row\'s menu', (tester) async {
|
||||
testWidgets('create from a row\'s menu', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
@@ -51,7 +52,7 @@ void main() {
|
||||
tester.assertNumberOfRowsInGridPage(4);
|
||||
});
|
||||
|
||||
testWidgets('with sort configured', (tester) async {
|
||||
testWidgets('create with sort configured', (tester) async {
|
||||
await tester.openTestDatabase(v069GridFileName);
|
||||
|
||||
// get grid data
|
||||
@@ -104,7 +105,7 @@ void main() {
|
||||
tester.assertNumberOfRowsInGridPage(14);
|
||||
});
|
||||
|
||||
testWidgets('with filter configured', (tester) async {
|
||||
testWidgets('create with filter configured', (tester) async {
|
||||
await tester.openTestDatabase(v069GridFileName);
|
||||
|
||||
// get grid data
|
||||
@@ -182,22 +183,52 @@ void main() {
|
||||
expect(actual, orderedEquals(original));
|
||||
});
|
||||
|
||||
// TODO(RS): move to somewhere else
|
||||
testWidgets('delete row of the grid', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.hoverOnFirstRowOfGrid(() async {
|
||||
// Open the row menu and then click the delete
|
||||
// Open the row menu and click the delete button
|
||||
await tester.tapRowMenuButtonInGrid();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapDeleteOnRowMenu();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 3 initial rows - 1 deleted
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
expect(find.byType(ConfirmPopup), findsOneWidget);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
|
||||
testWidgets('delete row in two views', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.renameLinkedView(
|
||||
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Grid),
|
||||
'grid 1',
|
||||
);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
|
||||
await tester.renameLinkedView(
|
||||
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Grid).at(1),
|
||||
'grid 2',
|
||||
);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.hoverOnFirstRowOfGrid(() async {
|
||||
// Open the row menu and click the delete button
|
||||
await tester.tapRowMenuButtonInGrid();
|
||||
await tester.tapDeleteOnRowMenu();
|
||||
});
|
||||
expect(find.byType(ConfirmPopup), findsOneWidget);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
// 3 initial rows - 1 deleted
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
|
||||
await tester.tapTabBarLinkedViewByViewName('grid 1');
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
});
|
||||
}
|
||||
+9
-6
@@ -1,11 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_shortcuts_view.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -16,8 +15,8 @@ import '../board/board_hide_groups_test.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('shortcuts test', () {
|
||||
testWidgets('can change and overwrite shortcut', (tester) async {
|
||||
group('shortcuts:', () {
|
||||
testWidgets('change and overwrite shortcut', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
@@ -29,7 +28,11 @@ void main() {
|
||||
LocaleKeys.settings_shortcutsPage_keybindings_backspace.tr();
|
||||
|
||||
// Input "Delete" into the search field
|
||||
await tester.enterText(find.byType(TextField), backspaceCmd);
|
||||
final inputField = find.descendant(
|
||||
of: find.byType(SettingsShortcutsView),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await tester.enterText(inputField, backspaceCmd);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.hoverOnWidget(
|
||||
|
||||
@@ -11,7 +11,7 @@ void main() {
|
||||
|
||||
const emoji = '😁';
|
||||
|
||||
group('Icon', () {
|
||||
group('Icon:', () {
|
||||
testWidgets('Update page icon in sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -52,6 +52,7 @@ void main() {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/board/presentation/board_page.dart';
|
||||
import 'package:appflowy/plugins/database/calendar/presentation/calendar_page.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
@@ -8,7 +7,6 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -17,7 +15,7 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('sidebar test', () {
|
||||
group('sidebar:', () {
|
||||
testWidgets('create a new page', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -26,9 +24,7 @@ void main() {
|
||||
await tester.tapNewPageButton();
|
||||
|
||||
// expect to see a new document
|
||||
tester.expectToSeePageName(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
);
|
||||
tester.expectToSeePageName('');
|
||||
// and with one paragraph block
|
||||
expect(find.byType(ParagraphBlockComponentWidget), findsOneWidget);
|
||||
});
|
||||
@@ -202,7 +198,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Grid,
|
||||
onHover: () async {
|
||||
expect(find.byType(ViewAddButton), findsNothing);
|
||||
expect(find.byType(ViewMoreActionButton), findsOneWidget);
|
||||
expect(find.byType(ViewMoreActionPopover), findsOneWidget);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
|
||||
import 'sidebar_icon_test.dart' as sidebar_icon_test;
|
||||
import 'sidebar_test.dart' as sidebar_test;
|
||||
import 'sidebar_view_item_test.dart' as sidebar_view_item_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -12,4 +13,5 @@ void main() {
|
||||
// sidebar_expanded_test.main();
|
||||
sidebar_favorite_test.main();
|
||||
sidebar_icon_test.main();
|
||||
sidebar_view_item_test.main();
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Sidebar view item tests', () {
|
||||
testWidgets('Access view item context menu by right click', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// Right click on the view item and change icon
|
||||
await tester.tap(find.byType(ViewItem), buttons: kSecondaryButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Change icon
|
||||
final changeIconButton =
|
||||
find.text(LocaleKeys.document_plugins_cover_changeIcon.tr());
|
||||
|
||||
await tester.tapButton(changeIconButton);
|
||||
await tester.pumpUntilFound(find.byType(FlowyEmojiPicker));
|
||||
|
||||
const emoji = '😁';
|
||||
await tester.tapEmoji(emoji);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tester.expectViewHasIcon(gettingStarted, ViewLayoutPB.Document, emoji);
|
||||
});
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@ import '../../shared/common_operations.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Zoom in/out: ', () {
|
||||
group('Zoom in/out:', () {
|
||||
Future<void> resetAppFlowyScaleFactor(
|
||||
WindowSizeManager windowSizeManager,
|
||||
) async {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner.dart' as document_test_runner;
|
||||
import 'desktop/document/document_test_runner_1.dart' as document_test_runner;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/uncategorized/switch_folder_test.dart' as switch_folder_test;
|
||||
|
||||
@@ -15,7 +15,7 @@ Future<void> runIntegration1OnDesktop() async {
|
||||
first_test.main();
|
||||
|
||||
switch_folder_test.main();
|
||||
document_test_runner.startTesting();
|
||||
document_test_runner.main();
|
||||
|
||||
// DON'T add more tests here. This is the first test runner for desktop.
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -3,12 +3,13 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'desktop/board/board_test_runner.dart' as board_test_runner;
|
||||
import 'desktop/database/database_row_cover_test.dart'
|
||||
as database_row_cover_test;
|
||||
import 'desktop/grid/grid_create_row_test.dart' as grid_create_row_test_runner;
|
||||
import 'desktop/grid/grid_edit_row_test.dart' as grid_edit_row_test_runner;
|
||||
import 'desktop/grid/grid_filter_and_sort_test.dart'
|
||||
as grid_filter_and_sort_test_runner;
|
||||
import 'desktop/grid/grid_reopen_test.dart' as grid_reopen_test_runner;
|
||||
import 'desktop/grid/grid_reorder_row_test.dart'
|
||||
as grid_reorder_row_test_runner;
|
||||
import 'desktop/grid/grid_row_test.dart' as grid_create_row_test_runner;
|
||||
import 'desktop/settings/settings_runner.dart' as settings_test_runner;
|
||||
import 'desktop/sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||
import 'desktop/uncategorized/emoji_shortcut_test.dart' as emoji_shortcut_test;
|
||||
@@ -44,6 +45,8 @@ Future<void> runIntegration3OnDesktop() async {
|
||||
grid_create_row_test_runner.main();
|
||||
grid_reorder_row_test_runner.main();
|
||||
grid_filter_and_sort_test_runner.main();
|
||||
|
||||
grid_edit_row_test_runner.main();
|
||||
zoom_in_out_test.main();
|
||||
|
||||
// DON'T add more tests here. Add them to desktop_runner_4.dart
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner_2.dart' as document_test_runner_2;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration4OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration4OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
first_test.main();
|
||||
|
||||
document_test_runner_2.main();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'document/publish_test.dart' as publish_test;
|
||||
import 'document/share_link_test.dart' as share_link_test;
|
||||
import 'workspace/workspace_operations_test.dart' as workspace_operations_test;
|
||||
|
||||
Future<void> main() async {
|
||||
workspace_operations_test.main();
|
||||
share_link_test.main();
|
||||
publish_test.main();
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('publish:', () {
|
||||
testWidgets('publish document', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.openMoreActionMenuOnMobile();
|
||||
|
||||
// click the publish button
|
||||
await tester.editor.clickMoreActionItemOnMobile(
|
||||
LocaleKeys.shareAction_publish.tr(),
|
||||
);
|
||||
|
||||
// wait the notification dismiss
|
||||
final publishSuccessText = find.findTextInFlowyText(
|
||||
LocaleKeys.publish_publishSuccessfully.tr(),
|
||||
);
|
||||
expect(publishSuccessText, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(publishSuccessText);
|
||||
|
||||
// open the menu again, to check the publish status
|
||||
await tester.editor.openMoreActionMenuOnMobile();
|
||||
// expect to see the unpublish button and the visit site button
|
||||
expect(
|
||||
find.text(LocaleKeys.shareAction_unPublish.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.text(LocaleKeys.shareAction_visitSite.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// unpublish the document
|
||||
await tester.editor.clickMoreActionItemOnMobile(
|
||||
LocaleKeys.shareAction_unPublish.tr(),
|
||||
);
|
||||
final unPublishSuccessText = find.findTextInFlowyText(
|
||||
LocaleKeys.publish_unpublishSuccessfully.tr(),
|
||||
);
|
||||
expect(unPublishSuccessText, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(unPublishSuccessText);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('share link:', () {
|
||||
testWidgets('copy share link and paste it on doc', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open the getting started page and paste the link
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// open the more action menu
|
||||
await tester.editor.openMoreActionMenuOnMobile();
|
||||
|
||||
// click the share link item
|
||||
await tester.editor.clickMoreActionItemOnMobile(
|
||||
LocaleKeys.shareAction_copyLink.tr(),
|
||||
);
|
||||
|
||||
// check the clipboard
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text,
|
||||
matches(appflowySharePageLinkPattern),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('workspace operations:', () {
|
||||
testWidgets('create a new workspace', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// click the create a new workspace button
|
||||
await tester.tapButton(find.text(Constants.defaultWorkspaceName));
|
||||
await tester.tapButton(find.text(LocaleKeys.workspace_create.tr()));
|
||||
|
||||
// input the new workspace name
|
||||
final inputField = find.byType(TextFormField);
|
||||
const newWorkspaceName = 'AppFlowy';
|
||||
await tester.enterText(inputField, newWorkspaceName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// wait for the workspace to be created
|
||||
await tester.pumpUntilFound(
|
||||
find.text(LocaleKeys.workspace_createSuccess.tr()),
|
||||
);
|
||||
|
||||
// expect to see the new workspace
|
||||
expect(find.text(newWorkspaceName), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'page_style_test.dart' as page_style_test;
|
||||
import 'title_test.dart' as title_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
title_test.main();
|
||||
page_style_test.main();
|
||||
}
|
||||
+5
-5
@@ -57,10 +57,11 @@ void main() {
|
||||
.single
|
||||
.widget as AppFlowyEditorPage;
|
||||
return editorPage.styleCustomizer
|
||||
.style()
|
||||
.textStyleConfiguration
|
||||
.text
|
||||
.height!;
|
||||
.style()
|
||||
.textStyleConfiguration
|
||||
.text
|
||||
.height ??
|
||||
PageStyleLineHeightLayout.normal.lineHeight;
|
||||
}
|
||||
|
||||
testWidgets('change font size in page style settings', (tester) async {
|
||||
@@ -126,7 +127,6 @@ void main() {
|
||||
|
||||
// click done button to exit the page style settings
|
||||
await tester.tapButton(find.byType(BottomSheetDoneButton).first);
|
||||
await tester.tapButton(find.byType(BottomSheetDoneButton).first);
|
||||
|
||||
// check the cover
|
||||
final builtInCover = find.descendant(
|
||||
@@ -0,0 +1,76 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document title:', () {
|
||||
testWidgets('create a new page, the title should be empty', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
|
||||
final createPageButton = find.byKey(
|
||||
BottomNavigationBarItemType.add.valueKey,
|
||||
);
|
||||
await tester.tapButton(createPageButton);
|
||||
expect(find.byType(MobileDocumentScreen), findsOneWidget);
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
expect(title, findsOneWidget);
|
||||
final textField = tester.widget<TextField>(title);
|
||||
expect(textField.focusNode!.hasFocus, isTrue);
|
||||
|
||||
// input new name and press done button
|
||||
const name = 'test document';
|
||||
await tester.enterText(title, name);
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
final newTitle = tester.editor.findDocumentTitle(name);
|
||||
expect(newTitle, findsOneWidget);
|
||||
expect(textField.controller!.text, name);
|
||||
|
||||
// the document should get focus
|
||||
final editor = tester.widget<AppFlowyEditorPage>(
|
||||
find.byType(AppFlowyEditorPage),
|
||||
);
|
||||
expect(
|
||||
editor.editorState.selection,
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+7
-7
@@ -3,10 +3,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/editor/mobile_editor_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/document_page.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
@@ -34,13 +35,12 @@ void main() {
|
||||
cloudType: AuthenticatorType.local,
|
||||
);
|
||||
|
||||
// click the anonymousSignInButton
|
||||
final anonymousSignInButton = find.byType(SignInAnonymousButtonV2);
|
||||
expect(anonymousSignInButton, findsOneWidget);
|
||||
await tester.tapButton(anonymousSignInButton);
|
||||
|
||||
// tap the create page button
|
||||
final createPageButton = find.byKey(mobileCreateNewPageButtonKey);
|
||||
final createPageButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowySvg &&
|
||||
widget.svg.path == FlowySvgs.m_home_unselected_m.path,
|
||||
);
|
||||
await tester.tapButton(createPageButton);
|
||||
expect(find.byType(MobileDocumentScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@@ -29,11 +29,6 @@ void main() {
|
||||
testWidgets('anon user and then sign in', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
|
||||
// click the anonymousSignInButton
|
||||
final anonymousSignInButton = find.byType(SignInAnonymousButtonV2);
|
||||
expect(anonymousSignInButton, findsOneWidget);
|
||||
await tester.tapButton(anonymousSignInButton);
|
||||
|
||||
// expect to see the home page
|
||||
expect(find.byType(MobileHomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'mobile/document/page_style_test.dart' as page_style_test;
|
||||
import 'mobile/home_page/create_new_page_test.dart' as create_new_page_test;
|
||||
import 'mobile/sign_in/anonymous_sign_in_test.dart' as anonymous_sign_in_test;
|
||||
|
||||
Future<void> runIntegrationOnMobile() async {
|
||||
Log.shared.disableLog = true;
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
anonymous_sign_in_test.main();
|
||||
create_new_page_test.main();
|
||||
page_style_test.main();
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'desktop_runner_1.dart';
|
||||
import 'desktop_runner_2.dart';
|
||||
import 'desktop_runner_3.dart';
|
||||
import 'desktop_runner_4.dart';
|
||||
import 'mobile_runner.dart';
|
||||
|
||||
/// The main task runner for all integration tests in AppFlowy.
|
||||
@@ -17,6 +18,7 @@ Future<void> main() async {
|
||||
await runIntegration1OnDesktop();
|
||||
await runIntegration2OnDesktop();
|
||||
await runIntegration3OnDesktop();
|
||||
await runIntegration4OnDesktop();
|
||||
} else if (Platform.isIOS || Platform.isAndroid) {
|
||||
await runIntegrationOnMobile();
|
||||
} else {
|
||||
|
||||
@@ -10,9 +10,10 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'util.dart';
|
||||
|
||||
extension AppFlowyAuthTest on WidgetTester {
|
||||
Future<void> tapGoogleLoginInButton() async {
|
||||
Future<void> tapGoogleLoginInButton({bool pumpAndSettle = true}) async {
|
||||
await tapButton(
|
||||
find.byKey(signInWithGoogleButtonKey),
|
||||
pumpAndSettle: pumpAndSettle,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class FlowyTestContext {
|
||||
FlowyTestContext({required this.applicationDataDirectory});
|
||||
@@ -105,7 +106,7 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> waitUntilSignInPageShow() async {
|
||||
if (isAuthEnabled) {
|
||||
if (isAuthEnabled || UniversalPlatform.isMobile) {
|
||||
final finder = find.byType(SignInAnonymousButtonV2);
|
||||
await pumpUntilFound(finder, timeout: const Duration(seconds: 30));
|
||||
expect(finder, findsOneWidget);
|
||||
@@ -158,23 +159,18 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
|
||||
Future<void> tapButton(
|
||||
Finder finder, {
|
||||
int? pointer,
|
||||
int buttons = kPrimaryButton,
|
||||
bool warnIfMissed = false,
|
||||
int milliseconds = 500,
|
||||
bool pumpAndSettle = true,
|
||||
}) async {
|
||||
await tap(
|
||||
finder,
|
||||
buttons: buttons,
|
||||
warnIfMissed: warnIfMissed,
|
||||
);
|
||||
await tap(finder, buttons: buttons, warnIfMissed: warnIfMissed);
|
||||
|
||||
if (pumpAndSettle) {
|
||||
await this.pumpAndSettle(
|
||||
Duration(milliseconds: milliseconds),
|
||||
EnginePhase.sendSemanticsUpdate,
|
||||
const Duration(seconds: 5),
|
||||
const Duration(seconds: 15),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,16 @@ import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_button.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_new_page_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||
@@ -39,6 +43,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../desktop/board/board_hide_groups_test.dart';
|
||||
import 'emoji.dart';
|
||||
import 'util.dart';
|
||||
|
||||
@@ -182,6 +187,16 @@ extension CommonOperations on WidgetTester {
|
||||
}
|
||||
}
|
||||
|
||||
/// Right click on the page name.
|
||||
Future<void> rightClickOnPageName(
|
||||
String name, {
|
||||
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||
}) async {
|
||||
final page = findPageName(name, layout: layout);
|
||||
await tap(page, buttons: kSecondaryMouseButton);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
/// open the page with given name.
|
||||
Future<void> openPage(
|
||||
String name, {
|
||||
@@ -196,8 +211,11 @@ extension CommonOperations on WidgetTester {
|
||||
///
|
||||
/// Must call [hoverOnPageName] first.
|
||||
Future<void> tapPageOptionButton() async {
|
||||
final optionButton = find.byType(ViewMoreActionButton);
|
||||
await tapButton(optionButton);
|
||||
final optionButton = find.descendant(
|
||||
of: find.byType(ViewMoreActionPopover),
|
||||
matching: find.byFlowySvg(FlowySvgs.workspace_three_dots_s),
|
||||
);
|
||||
await tapButton(optionButton, warnIfMissed: true);
|
||||
}
|
||||
|
||||
/// Tap the delete page button.
|
||||
@@ -237,6 +255,10 @@ extension CommonOperations on WidgetTester {
|
||||
await tapOKButton();
|
||||
}
|
||||
|
||||
Future<void> tapTrashButton() async {
|
||||
await tap(find.byType(SidebarTrashButton));
|
||||
}
|
||||
|
||||
Future<void> tapOKButton() async {
|
||||
final okButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
@@ -253,7 +275,10 @@ extension CommonOperations on WidgetTester {
|
||||
}) async {
|
||||
final page = findPageName(pageName, layout: layout);
|
||||
await hoverOnWidget(page);
|
||||
final expandButton = find.byType(ViewItemDefaultLeftIcon);
|
||||
final expandButton = find.descendant(
|
||||
of: page,
|
||||
matching: find.byType(ViewItemDefaultLeftIcon),
|
||||
);
|
||||
await tapButton(expandButton.first);
|
||||
}
|
||||
|
||||
@@ -269,12 +294,14 @@ extension CommonOperations on WidgetTester {
|
||||
|
||||
/// Tap the delete permanently button.
|
||||
///
|
||||
/// the restore button will show after the current page is deleted.
|
||||
/// the delete permanently button will show after the current page is deleted.
|
||||
Future<void> tapDeletePermanentlyButton() async {
|
||||
final restoreButton = find.textContaining(
|
||||
final deleteButton = find.textContaining(
|
||||
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
||||
);
|
||||
await tapButton(restoreButton);
|
||||
await tapButton(deleteButton);
|
||||
await tap(find.text(LocaleKeys.button_delete.tr()));
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Tap the share button above the document page.
|
||||
@@ -317,7 +344,7 @@ extension CommonOperations on WidgetTester {
|
||||
// hover on it and change it's name
|
||||
if (name != null) {
|
||||
await hoverOnPageName(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout.defaultName,
|
||||
layout: layout,
|
||||
onHover: () async {
|
||||
await renamePage(name);
|
||||
@@ -331,13 +358,39 @@ extension CommonOperations on WidgetTester {
|
||||
if (openAfterCreated) {
|
||||
await openPage(
|
||||
// if the name is null, use the default name
|
||||
name ?? LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
name ?? layout.defaultName,
|
||||
layout: layout,
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createOpenRenameDocumentUnderParent({
|
||||
required String name,
|
||||
String? parentName,
|
||||
}) async {
|
||||
// create a new page
|
||||
await tapAddViewButton(name: parentName ?? gettingStarted);
|
||||
await tapButtonWithName(ViewLayoutPB.Document.menuName);
|
||||
final settingsOrFailure = await getIt<KeyValueStorage>().getWithFormat(
|
||||
KVKeys.showRenameDialogWhenCreatingNewFile,
|
||||
(value) => bool.parse(value),
|
||||
);
|
||||
final showRenameDialog = settingsOrFailure ?? false;
|
||||
if (showRenameDialog) {
|
||||
await tapOKButton();
|
||||
}
|
||||
await pumpAndSettle();
|
||||
|
||||
// open the page after created
|
||||
await openPage(ViewLayoutPB.Document.defaultName);
|
||||
await pumpAndSettle();
|
||||
|
||||
// Enter new name in the document title
|
||||
await enterText(find.byType(TextFieldWithMetricLines), name);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Create a new page in the space
|
||||
Future<void> createNewPageInSpace({
|
||||
required String spaceName,
|
||||
@@ -368,7 +421,7 @@ extension CommonOperations on WidgetTester {
|
||||
|
||||
// hover on new created page and change it's name
|
||||
await hoverOnPageName(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
'',
|
||||
layout: layout,
|
||||
onHover: () async {
|
||||
await renamePage(pageName);
|
||||
@@ -398,6 +451,19 @@ extension CommonOperations on WidgetTester {
|
||||
await tapButton(addPageButton);
|
||||
}
|
||||
|
||||
/// Click the + button in the space header
|
||||
Future<void> clickSpaceHeader() async {
|
||||
await tapButton(find.byType(SidebarSpaceHeader));
|
||||
}
|
||||
|
||||
Future<void> openSpace(String spaceName) async {
|
||||
final space = find.descendant(
|
||||
of: find.byType(SidebarSpaceMenuItem),
|
||||
matching: find.text(spaceName),
|
||||
);
|
||||
await tapButton(space);
|
||||
}
|
||||
|
||||
/// Create a new page on the top level
|
||||
Future<void> createNewPage({
|
||||
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||
@@ -629,7 +695,11 @@ extension CommonOperations on WidgetTester {
|
||||
expect(createWorkspaceDialog, findsOneWidget);
|
||||
|
||||
// input the workspace name
|
||||
await enterText(find.byType(TextField), name);
|
||||
final workspaceNameInput = find.descendant(
|
||||
of: createWorkspaceDialog,
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await enterText(workspaceNameInput, name);
|
||||
|
||||
await tapButtonWithName(LocaleKeys.button_ok.tr(), pumpAndSettle: false);
|
||||
await pump(const Duration(seconds: 5));
|
||||
|
||||
@@ -3,4 +3,6 @@ class Constants {
|
||||
static const gettingStartedPageName = 'Getting started';
|
||||
static const toDosPageName = 'To-dos';
|
||||
static const generalSpaceName = 'General';
|
||||
|
||||
static const defaultWorkspaceName = 'My Workspace';
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -20,13 +25,14 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checklist.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/choicechip.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/date.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/create_filter_list.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/disclosure_button.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_menu_item.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/footer/grid_footer.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/row.dart';
|
||||
@@ -147,6 +153,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
|
||||
expect(cell, findsOneWidget);
|
||||
await enterText(cell, input);
|
||||
await testTextInput.receiveAction(TextInputAction.done);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
@@ -242,10 +249,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> assertMultiSelectOption({
|
||||
void assertMultiSelectOption({
|
||||
required int rowIndex,
|
||||
required List<String> contents,
|
||||
}) async {
|
||||
}) {
|
||||
final findCell = cellFinder(rowIndex, FieldType.MultiSelect);
|
||||
for (final content in contents) {
|
||||
if (content.isNotEmpty) {
|
||||
@@ -406,17 +413,20 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> selectOption({required String name}) async {
|
||||
final option = find.byWidgetPredicate(
|
||||
(widget) => widget is SelectOptionTagCell && widget.option.name == name,
|
||||
final option = find.descendant(
|
||||
of: find.byType(SelectOptionCellEditor),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) => widget is SelectOptionTagCell && widget.option.name == name,
|
||||
),
|
||||
);
|
||||
|
||||
await tapButton(option);
|
||||
}
|
||||
|
||||
Future<void> findSelectOptionWithNameInGrid({
|
||||
void findSelectOptionWithNameInGrid({
|
||||
required int rowIndex,
|
||||
required String name,
|
||||
}) async {
|
||||
}) {
|
||||
final findRow = find.byType(GridRow);
|
||||
final option = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
@@ -428,10 +438,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
expect(cell, findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> assertNumberOfSelectedOptionsInGrid({
|
||||
void assertNumberOfSelectedOptionsInGrid({
|
||||
required int rowIndex,
|
||||
required Matcher matcher,
|
||||
}) async {
|
||||
}) {
|
||||
final findRow = find.byType(GridRow);
|
||||
|
||||
final options = find.byWidgetPredicate(
|
||||
@@ -499,6 +509,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> renameChecklistTask({
|
||||
required int index,
|
||||
required String name,
|
||||
bool enter = true,
|
||||
}) async {
|
||||
final textField = find
|
||||
.descendant(
|
||||
@@ -508,7 +519,9 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
.at(index);
|
||||
|
||||
await enterText(textField, name);
|
||||
await testTextInput.receiveAction(TextInputAction.done);
|
||||
if (enter) {
|
||||
await testTextInput.receiveAction(TextInputAction.done);
|
||||
}
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
@@ -526,14 +539,32 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> deleteChecklistTask({required int index}) async {
|
||||
final task = find.byType(ChecklistItem).at(index);
|
||||
|
||||
await startGesture(getCenter(task), kind: PointerDeviceKind.mouse);
|
||||
await pumpAndSettle();
|
||||
|
||||
final button = find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
|
||||
await hoverOnWidget(
|
||||
task,
|
||||
onHover: () async {
|
||||
final button = find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
|
||||
);
|
||||
await tapButton(button);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tapButton(button);
|
||||
void assertPhantomChecklistItemAtIndex({required int index}) {
|
||||
final paddings = find.descendant(
|
||||
of: find.descendant(
|
||||
of: find.byType(ChecklistRowDetailCell),
|
||||
matching: find.byType(ReorderableListView),
|
||||
),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is Padding &&
|
||||
(widget.child is ChecklistItem ||
|
||||
widget.child is PhantomChecklistItem),
|
||||
),
|
||||
);
|
||||
final phantom = widget<Padding>(paddings.at(index)).child!;
|
||||
expect(phantom is PhantomChecklistItem, true);
|
||||
}
|
||||
|
||||
Future<void> openFirstRowDetailPage() async {
|
||||
@@ -679,6 +710,53 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await dismissFieldEditor();
|
||||
}
|
||||
|
||||
Future<void> changeFieldIcon(String icon) async {
|
||||
await tapButton(find.byType(FieldEditIconButton));
|
||||
if (icon.isEmpty) {
|
||||
final button = find.descendant(
|
||||
of: find.byType(FlowyIconEmojiPicker),
|
||||
matching: find.text(
|
||||
LocaleKeys.button_remove.tr(),
|
||||
),
|
||||
);
|
||||
await tapButton(button);
|
||||
} else {
|
||||
final svgContent = kIconGroups?.findSvgContent(icon);
|
||||
await tapButton(
|
||||
find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svgString == svgContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void assertFieldSvg(String name, FieldType fieldType) {
|
||||
final svgFinder = find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svg == fieldType.svgData,
|
||||
);
|
||||
final fieldButton = find.byWidgetPredicate(
|
||||
(widget) => widget is FieldCellButton && widget.field.name == name,
|
||||
);
|
||||
expect(
|
||||
find.descendant(of: fieldButton, matching: svgFinder),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
||||
|
||||
void assertFieldCustomSvg(String name, String svg) {
|
||||
final svgContent = kIconGroups?.findSvgContent(svg);
|
||||
final svgFinder = find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svgString == svgContent,
|
||||
);
|
||||
final fieldButton = find.byWidgetPredicate(
|
||||
(widget) => widget is FieldCellButton && widget.field.name == name,
|
||||
);
|
||||
expect(
|
||||
find.descendant(of: fieldButton, matching: svgFinder),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> changeCalculateAtIndex(int index, CalculationType type) async {
|
||||
await tap(find.byType(CalculateCell).at(index));
|
||||
await pumpAndSettle();
|
||||
@@ -957,7 +1035,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> tapCreateFilterByFieldType(FieldType type, String title) async {
|
||||
final findFilter = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is GridFilterPropertyCell &&
|
||||
widget is FilterableFieldButton &&
|
||||
widget.fieldInfo.fieldType == type &&
|
||||
widget.fieldInfo.name == title,
|
||||
);
|
||||
@@ -965,8 +1043,9 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> tapFilterButtonInGrid(String name) async {
|
||||
final findFilter = find.byType(FilterMenuItem);
|
||||
final button = find.descendant(of: findFilter, matching: find.text(name));
|
||||
final button = find.byWidgetPredicate(
|
||||
(widget) => widget is ChoiceChipButton && widget.fieldInfo.name == name,
|
||||
);
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
@@ -1004,12 +1083,15 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
/// Must call [tapSortMenuInSettingBar] first.
|
||||
Future<void> tapSortButtonByName(String name) async {
|
||||
final findSortItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DatabaseSortItem && widget.sortInfo.fieldInfo.name == name,
|
||||
Future<void> tapEditSortConditionButtonByFieldName(String name) async {
|
||||
final sortItem = find.descendant(
|
||||
of: find.ancestor(
|
||||
of: find.text(name),
|
||||
matching: find.byType(DatabaseSortItem),
|
||||
),
|
||||
matching: find.byType(SortConditionButton),
|
||||
);
|
||||
await tapButton(findSortItem);
|
||||
await tapButton(sortItem);
|
||||
}
|
||||
|
||||
/// Must call [tapSortMenuInSettingBar] first.
|
||||
@@ -1017,18 +1099,26 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
(FieldType, String) from,
|
||||
(FieldType, String) to,
|
||||
) async {
|
||||
final fromSortItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DatabaseSortItem &&
|
||||
widget.sortInfo.fieldInfo.fieldType == from.$1 &&
|
||||
widget.sortInfo.fieldInfo.name == from.$2,
|
||||
final fromSortItem = find.ancestor(
|
||||
of: find.text(from.$2),
|
||||
matching: find.byType(DatabaseSortItem),
|
||||
);
|
||||
final toSortItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DatabaseSortItem &&
|
||||
widget.sortInfo.fieldInfo.fieldType == to.$1 &&
|
||||
widget.sortInfo.fieldInfo.name == to.$2,
|
||||
final toSortItem = find.ancestor(
|
||||
of: find.text(to.$2),
|
||||
matching: find.byType(DatabaseSortItem),
|
||||
);
|
||||
// final fromSortItem = find.byWidgetPredicate(
|
||||
// (widget) =>
|
||||
// widget is DatabaseSortItem &&
|
||||
// widget.sort.fieldInfo.fieldType == from.$1 &&
|
||||
// widget.sort.fieldInfo.name == from.$2,
|
||||
// );
|
||||
// final toSortItem = find.byWidgetPredicate(
|
||||
// (widget) =>
|
||||
// widget is DatabaseSortItem &&
|
||||
// widget.sort.fieldInfo.fieldType == to.$1 &&
|
||||
// widget.sort.fieldInfo.name == to.$2,
|
||||
// );
|
||||
final dragElement = find.descendant(
|
||||
of: fromSortItem,
|
||||
matching: find.byType(ReorderableDragStartListener),
|
||||
@@ -1037,16 +1127,13 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await pumpAndSettle(const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
/// Must call [tapSortButtonByName] first.
|
||||
/// Must call [tapEditSortConditionButtonByFieldName] first.
|
||||
Future<void> tapSortByDescending() async {
|
||||
await tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(OrderPannelItem),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowyText &&
|
||||
widget.text == LocaleKeys.grid_sort_descending.tr(),
|
||||
),
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is OrderPanelItem &&
|
||||
widget.condition == SortConditionPB.Descending,
|
||||
),
|
||||
);
|
||||
await sendKeyEvent(LogicalKeyboardKey.escape);
|
||||
@@ -1129,6 +1216,44 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
Future<void> changeTextFilterCondition(
|
||||
TextFilterConditionPB condition,
|
||||
) async {
|
||||
await tapButton(find.byType(TextFilterConditionList));
|
||||
final button = find.descendant(
|
||||
of: find.byType(HoverButton),
|
||||
matching: find.text(
|
||||
condition.filterName,
|
||||
),
|
||||
);
|
||||
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
Future<void> changeSelectFilterCondition(
|
||||
SelectOptionFilterConditionPB condition,
|
||||
) async {
|
||||
await tapButton(find.byType(SelectOptionFilterConditionList));
|
||||
final button = find.descendant(
|
||||
of: find.byType(HoverButton),
|
||||
matching: find.text(condition.i18n),
|
||||
);
|
||||
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
Future<void> changeDateFilterCondition(
|
||||
DateTimeFilterCondition condition,
|
||||
) async {
|
||||
await tapButton(find.byType(DateFilterConditionList));
|
||||
final button = find.descendant(
|
||||
of: find.byType(HoverButton),
|
||||
matching: find.text(condition.filterName),
|
||||
);
|
||||
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
/// Should call [tapDatabaseSettingButton] first.
|
||||
Future<void> tapViewPropertiesButton() async {
|
||||
final findSettingItem = find.byType(DatabaseSettingsList);
|
||||
|
||||
@@ -2,23 +2,30 @@ import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option/option_actions.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_title.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
@@ -43,7 +50,10 @@ class EditorOperations {
|
||||
Future<void> tapLineOfEditorAt(int index) async {
|
||||
final textBlocks = find.byType(AppFlowyRichText);
|
||||
index = index.clamp(0, textBlocks.evaluate().length - 1);
|
||||
await tester.tapAt(tester.getTopRight(textBlocks.at(index)));
|
||||
final center = tester.getCenter(textBlocks.at(index));
|
||||
final right = tester.getTopRight(textBlocks.at(index));
|
||||
final centerRight = Offset(right.dx, center.dy);
|
||||
await tester.tapAt(centerRight);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
@@ -65,6 +75,20 @@ class EditorOperations {
|
||||
expect(find.byType(FlowyEmojiPicker), findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> paste() async {
|
||||
if (UniversalPlatform.isMacOS) {
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
} else {
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> tapGettingStartedIcon() async {
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
@@ -173,6 +197,11 @@ class EditorOperations {
|
||||
await tester.ime.insertCharacter('@');
|
||||
}
|
||||
|
||||
/// trigger the plus action menu (+) command
|
||||
Future<void> showPlusMenu() async {
|
||||
await tester.ime.insertCharacter('+');
|
||||
}
|
||||
|
||||
/// Tap the slash menu item with [name]
|
||||
///
|
||||
/// Must call [showSlashMenu] first.
|
||||
@@ -211,7 +240,7 @@ class EditorOperations {
|
||||
}
|
||||
|
||||
/// Update the editor's selection
|
||||
Future<void> updateSelection(Selection selection) async {
|
||||
Future<void> updateSelection(Selection? selection) async {
|
||||
final editorState = getCurrentEditorState();
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
@@ -269,10 +298,42 @@ class EditorOperations {
|
||||
widget.blockComponentContext.node.path.equals(path),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(PopoverActionList));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// open the turn into menu
|
||||
Future<void> openTurnIntoMenu(Path path) async {
|
||||
await hoverAndClickOptionMenuButton(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_turnInto.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(TurnIntoOptionMenu));
|
||||
}
|
||||
|
||||
/// copy link to block
|
||||
Future<void> copyLinkToBlock(Path path) async {
|
||||
await hoverAndClickOptionMenuButton(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> openDepthMenu(Path path) async {
|
||||
await hoverAndClickOptionMenuButton(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_depth.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(DepthOptionMenu));
|
||||
}
|
||||
|
||||
/// Drag block
|
||||
///
|
||||
/// [offset] is the offset to move the block.
|
||||
@@ -323,4 +384,54 @@ class EditorOperations {
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
}
|
||||
|
||||
Finder findDocumentTitle(String? title) {
|
||||
final parent = UniversalPlatform.isDesktop
|
||||
? find.byType(CoverTitle)
|
||||
: find.byType(DocumentImmersiveCover);
|
||||
|
||||
return find.descendant(
|
||||
of: parent,
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) {
|
||||
if (widget is! TextField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (widget.controller?.text == title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (title.isEmpty) {
|
||||
return widget.controller?.text.isEmpty ?? false;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// open the more action menu on mobile
|
||||
Future<void> openMoreActionMenuOnMobile() async {
|
||||
final moreActionButton = find.byType(MobileViewPageMoreButton);
|
||||
await tester.tapButton(moreActionButton);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// click the more action item on mobile
|
||||
///
|
||||
/// rename, add collaborator, publish, delete, etc.
|
||||
Future<void> clickMoreActionItemOnMobile(String name) async {
|
||||
final moreActionItem = find.descendant(
|
||||
of: find.byType(MobileQuickActionButton),
|
||||
matching: find.findTextInFlowyText(name),
|
||||
);
|
||||
await tester.tapButton(moreActionItem);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/banner.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||
@@ -25,9 +26,15 @@ const String gettingStarted = 'Getting started';
|
||||
extension Expectation on WidgetTester {
|
||||
/// Expect to see the home page and with a default read me page.
|
||||
Future<void> expectToSeeHomePageWithGetStartedPage() async {
|
||||
final finder = find.byType(HomeStack);
|
||||
await pumpUntilFound(finder);
|
||||
expect(finder, findsOneWidget);
|
||||
if (UniversalPlatform.isDesktopOrWeb) {
|
||||
final finder = find.byType(HomeStack);
|
||||
await pumpUntilFound(finder);
|
||||
expect(finder, findsOneWidget);
|
||||
} else if (UniversalPlatform.isMobile) {
|
||||
final finder = find.byType(MobileHomePage);
|
||||
await pumpUntilFound(finder);
|
||||
expect(finder, findsOneWidget);
|
||||
}
|
||||
|
||||
final docFinder = find.textContaining(gettingStarted);
|
||||
await pumpUntilFound(docFinder);
|
||||
|
||||
@@ -56,8 +56,6 @@ PODS:
|
||||
- Flutter
|
||||
- keyboard_height_plugin (0.0.1):
|
||||
- Flutter
|
||||
- open_file_ios (0.0.1):
|
||||
- Flutter
|
||||
- open_filex (0.0.2):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
@@ -104,7 +102,6 @@ DEPENDENCIES:
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||
- keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`)
|
||||
- open_file_ios (from `.symlinks/plugins/open_file_ios/ios`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
@@ -151,8 +148,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
||||
keyboard_height_plugin:
|
||||
:path: ".symlinks/plugins/keyboard_height_plugin/ios"
|
||||
open_file_ios:
|
||||
:path: ".symlinks/plugins/open_file_ios/ios"
|
||||
open_filex:
|
||||
:path: ".symlinks/plugins/open_filex/ios"
|
||||
package_info_plus:
|
||||
@@ -189,7 +184,6 @@ SPEC CHECKSUMS:
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
open_file_ios: 461db5853723763573e140de3193656f91990d9e
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
|
||||
@@ -57,8 +57,6 @@ class KVKeys {
|
||||
|
||||
static const String kCloudType = 'kCloudType';
|
||||
static const String kAppflowyCloudBaseURL = 'kAppFlowyCloudBaseURL';
|
||||
static const String kSupabaseURL = 'kSupabaseURL';
|
||||
static const String kSupabaseAnonKey = 'kSupabaseAnonKey';
|
||||
|
||||
/// The key for saving the text scale factor.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/startup/tasks/device_info_task.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class CocoaWindowChannel {
|
||||
CocoaWindowChannel._();
|
||||
@@ -44,7 +44,14 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!Platform.isMacOS) {
|
||||
// the frameless window is only supported on macOS
|
||||
if (!UniversalPlatform.isMacOS) {
|
||||
return widget.child ?? const SizedBox.shrink();
|
||||
}
|
||||
|
||||
// For the macOS version 15 or higher, we can control the window position by using system APIs
|
||||
if (ApplicationInfo.macOSMajorVersion != null &&
|
||||
ApplicationInfo.macOSMajorVersion! >= 15) {
|
||||
return widget.child ?? const SizedBox.shrink();
|
||||
}
|
||||
|
||||
|
||||
+6
-24
@@ -14,8 +14,12 @@ import 'package:appflowy_backend/log.dart';
|
||||
/// [ty] - The type of cloud to be set. It must be one of the values from
|
||||
/// [AuthenticatorType] enum. The corresponding integer value of the enum is stored:
|
||||
/// - `CloudType.local` is stored as "0".
|
||||
/// - `CloudType.supabase` is stored as "1".
|
||||
/// - `CloudType.appflowyCloud` is stored as "2".
|
||||
///
|
||||
/// The gap between [AuthenticatorType.local] and [AuthenticatorType.appflowyCloud] is
|
||||
/// due to previously supporting Supabase, this has been deprecated since and removed.
|
||||
/// To not cause conflicts with older clients, we keep the gap.
|
||||
///
|
||||
Future<void> _setAuthenticatorType(AuthenticatorType ty) async {
|
||||
switch (ty) {
|
||||
case AuthenticatorType.local:
|
||||
@@ -84,7 +88,7 @@ Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
/// A boolean value indicating whether authentication is enabled. It returns
|
||||
/// `true` if the application is in release or develop mode, and the cloud type
|
||||
/// is not set to `CloudType.local`. Additionally, it checks if either the
|
||||
/// AppFlowy Cloud or Supabase configuration is valid.
|
||||
/// AppFlowy Cloud configuration is valid.
|
||||
/// Returns `false` otherwise.
|
||||
bool get isAuthEnabled {
|
||||
final env = getIt<AppFlowyCloudSharedEnv>();
|
||||
@@ -286,25 +290,3 @@ Future<String> _getAppFlowyCloudWSUrl(String baseURL) async {
|
||||
Future<String> _getAppFlowyCloudGotrueUrl(String baseURL) async {
|
||||
return "$baseURL/gotrue";
|
||||
}
|
||||
|
||||
Future<void> setSupabaseServer(
|
||||
String? url,
|
||||
String? anonKey,
|
||||
) async {
|
||||
assert(
|
||||
(url != null && anonKey != null) || (url == null && anonKey == null),
|
||||
"Either both Supabase URL and anon key must be set, or both should be unset",
|
||||
);
|
||||
|
||||
if (url == null) {
|
||||
await getIt<KeyValueStorage>().remove(KVKeys.kSupabaseURL);
|
||||
} else {
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kSupabaseURL, url);
|
||||
}
|
||||
|
||||
if (anonKey == null) {
|
||||
await getIt<KeyValueStorage>().remove(KVKeys.kSupabaseAnonKey);
|
||||
} else {
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kSupabaseAnonKey, anonKey);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ import 'package:scaled_app/scaled_app.dart';
|
||||
import 'startup/startup.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
ScaledWidgetsFlutterBinding.ensureInitialized(scaleFactor: (_) => 1.0);
|
||||
ScaledWidgetsFlutterBinding.ensureInitialized(
|
||||
scaleFactor: (_) => 1.0,
|
||||
);
|
||||
|
||||
await runAppFlowy();
|
||||
}
|
||||
+2
-2
@@ -56,7 +56,7 @@ class NotificationReminderBloc
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
pageTitle: view.nameOrDefault,
|
||||
view: view,
|
||||
reminderContent: node.delta?.toPlainText() ?? '',
|
||||
nodes: [node],
|
||||
@@ -68,7 +68,7 @@ class NotificationReminderBloc
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
pageTitle: view.nameOrDefault,
|
||||
view: view,
|
||||
reminderContent: reminder.message,
|
||||
status: NotificationReminderStatus.loaded,
|
||||
|
||||
+11
@@ -441,4 +441,15 @@ class PageStyleCover {
|
||||
bool get isCustomImage => type == PageStyleCoverImageType.customImage;
|
||||
bool get isUnsplashImage => type == PageStyleCoverImageType.unsplashImage;
|
||||
bool get isLocalImage => type == PageStyleCoverImageType.localImage;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! PageStyleCover) {
|
||||
return false;
|
||||
}
|
||||
return type == other.type && value == other.value;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(type, value);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/document_collaborators.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
@@ -94,6 +95,10 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
ShareBloc(view: view)..add(const ShareEvent.initial()),
|
||||
),
|
||||
if (view.layout.isDocumentView)
|
||||
BlocProvider(
|
||||
create: (_) => DocumentPageStyleBloc(view: view)
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:appflowy/mobile/presentation/base/view_page/more_bottom_sheet.da
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
@@ -98,6 +99,8 @@ class MobileViewPageMoreButton extends StatelessWidget {
|
||||
providers: [
|
||||
BlocProvider.value(value: context.read<ViewBloc>()),
|
||||
BlocProvider.value(value: context.read<FavoriteBloc>()),
|
||||
BlocProvider.value(value: context.read<MobileViewPageBloc>()),
|
||||
BlocProvider.value(value: context.read<ShareBloc>()),
|
||||
],
|
||||
child: MobileViewPageMoreBottomSheet(view: view),
|
||||
),
|
||||
|
||||
Loaded 100 of 947 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user