mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-23 22:27:13 -04:00
Merge branch 'main' into test/filter-calculations
This commit is contained in:
commit
f03d8d35ef
1274 changed files with 63802 additions and 22547 deletions
287
.github/workflows/android_ci.yaml.bak
vendored
287
.github/workflows/android_ci.yaml.bak
vendored
|
@ -1,126 +1,197 @@
|
|||
# name: Android CI
|
||||
name: Android CI
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - "main"
|
||||
# paths:
|
||||
# - ".github/workflows/mobile_ci.yaml"
|
||||
# - "frontend/**"
|
||||
# - "!frontend/appflowy_tauri/**"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- ".github/workflows/mobile_ci.yaml"
|
||||
- "frontend/**"
|
||||
- "!frontend/appflowy_tauri/**"
|
||||
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - "main"
|
||||
# paths:
|
||||
# - ".github/workflows/mobile_ci.yaml"
|
||||
# - "frontend/**"
|
||||
# - "!frontend/appflowy_tauri/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- ".github/workflows/mobile_ci.yaml"
|
||||
- "frontend/**"
|
||||
- "!frontend/appflowy_tauri/**"
|
||||
|
||||
# env:
|
||||
# CARGO_TERM_COLOR: always
|
||||
# FLUTTER_VERSION: "3.22.0"
|
||||
# RUST_TOOLCHAIN: "1.77.2"
|
||||
# CARGO_MAKE_VERSION: "0.36.6"
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FLUTTER_VERSION: "3.22.3"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
CARGO_MAKE_VERSION: "0.37.18"
|
||||
CLOUD_VERSION: 0.6.54-amd64
|
||||
|
||||
# concurrency:
|
||||
# group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
# cancel-in-progress: true
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# jobs:
|
||||
# build:
|
||||
# if: github.event.pull_request.draft != true
|
||||
# strategy:
|
||||
# fail-fast: true
|
||||
# matrix:
|
||||
# os: [macos-14]
|
||||
# runs-on: ${{ matrix.os }}
|
||||
jobs:
|
||||
build:
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# steps:
|
||||
# - name: Check storage space
|
||||
# run: df -h
|
||||
steps:
|
||||
- name: Check storage space
|
||||
run:
|
||||
df -h
|
||||
|
||||
# # the following step is required to avoid running out of space
|
||||
# - name: Maximize build space
|
||||
# if: matrix.os == 'ubuntu-latest'
|
||||
# run: |
|
||||
# sudo rm -rf /usr/share/dotnet
|
||||
# sudo rm -rf /opt/ghc
|
||||
# sudo rm -rf "/usr/local/share/boost"
|
||||
# sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
# sudo docker image prune --all --force
|
||||
# sudo rm -rf /opt/hostedtoolcache/codeQL
|
||||
# sudo rm -rf ${GITHUB_WORKSPACE}/.git
|
||||
# sudo rm -rf $ANDROID_HOME/ndk
|
||||
# the following step is required to avoid running out of space
|
||||
- name: Maximize build space
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo docker image prune --all --force
|
||||
sudo rm -rf /opt/hostedtoolcache/codeQL
|
||||
sudo rm -rf ${GITHUB_WORKSPACE}/.git
|
||||
|
||||
# - name: Check storage space
|
||||
# run: df -h
|
||||
- name: Check storage space
|
||||
run: df -h
|
||||
|
||||
# - name: Checkout source code
|
||||
# uses: actions/checkout@v4
|
||||
- name: Checkout appflowy cloud code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AppFlowy-IO/AppFlowy-Cloud
|
||||
path: AppFlowy-Cloud
|
||||
|
||||
# - uses: actions/setup-java@v4
|
||||
# with:
|
||||
# distribution: temurin
|
||||
# java-version: 11
|
||||
- name: Prepare appflowy cloud env
|
||||
working-directory: AppFlowy-Cloud
|
||||
run: |
|
||||
# log level
|
||||
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: Install Rust toolchain
|
||||
# id: rust_toolchain
|
||||
# uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
# override: true
|
||||
# profile: minimal
|
||||
- name: Run Docker-Compose
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
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
|
||||
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Restarting with the correct version..."
|
||||
# Remove all containers if any exist
|
||||
if [ "$(docker ps -aq)" ]; then
|
||||
docker rm -f $(docker ps -aq)
|
||||
else
|
||||
echo "No containers to remove."
|
||||
fi
|
||||
|
||||
# - name: Install flutter
|
||||
# id: flutter
|
||||
# uses: subosito/flutter-action@v2
|
||||
# with:
|
||||
# channel: "stable"
|
||||
# flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
# Remove all volumes if any exist
|
||||
if [ "$(docker volume ls -q)" ]; then
|
||||
docker volume rm $(docker volume ls -q)
|
||||
else
|
||||
echo "No volumes to remove."
|
||||
fi
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
docker ps -a
|
||||
docker compose logs
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
fi
|
||||
|
||||
# - uses: gradle/gradle-build-action@v3
|
||||
# with:
|
||||
# gradle-version: 7.4.2
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# - uses: davidB/rust-cargo-make@v1
|
||||
# with:
|
||||
# version: "0.36.6"
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
|
||||
# - name: Install prerequisites
|
||||
# working-directory: frontend
|
||||
# run: |
|
||||
# rustup target install aarch64-linux-android
|
||||
# rustup target install x86_64-linux-android
|
||||
# cargo install --force duckscript_cli
|
||||
# cargo install cargo-ndk
|
||||
# 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
|
||||
# sudo apt-get install keybinder-3.0 libnotify-dev
|
||||
# sudo apt-get install gcc-multilib
|
||||
# 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
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
# - name: Build AppFlowy
|
||||
# working-directory: frontend
|
||||
# run: |
|
||||
# cargo make --profile development-android appflowy-android-dev-ci
|
||||
- name: Install flutter
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- uses: gradle/gradle-build-action@v3
|
||||
with:
|
||||
gradle-version: 8.10
|
||||
|
||||
# - name: Run integration tests
|
||||
# # https://github.com/ReactiveCircus/android-emulator-runner
|
||||
# uses: reactivecircus/android-emulator-runner@v2
|
||||
# with:
|
||||
# api-level: 32
|
||||
# arch: arm64-v8a
|
||||
# disk-size: 2048M
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
# script: flutter test integration_test/runner.dart
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
with:
|
||||
version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
|
||||
- name: Install prerequisites
|
||||
working-directory: frontend
|
||||
run: |
|
||||
rustup target install aarch64-linux-android
|
||||
rustup target install x86_64-linux-android
|
||||
rustup target add armv7-linux-androideabi
|
||||
cargo install --force --locked duckscript_cli
|
||||
cargo install cargo-ndk
|
||||
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
|
||||
sudo apt-get install keybinder-3.0 libnotify-dev
|
||||
sudo apt-get install gcc-multilib
|
||||
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
|
||||
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make --profile development-android appflowy-core-dev-android
|
||||
cargo make --profile development-android code_generation
|
||||
cd rust-lib
|
||||
cargo clean
|
||||
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Run integration tests
|
||||
# https://github.com/ReactiveCircus/android-emulator-runner
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 33
|
||||
arch: x86_64
|
||||
disk-size: 2048M
|
||||
working-directory: frontend/appflowy_flutter
|
||||
disable-animations: true
|
||||
force-avd-creation: false
|
||||
target: google_apis
|
||||
script: flutter test integration_test/mobile/cloud/cloud_runner.dart
|
||||
|
|
4
.github/workflows/docker_ci.yml
vendored
4
.github/workflows/docker_ci.yml
vendored
|
@ -2,9 +2,9 @@ name: Docker-CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "release/*"]
|
||||
branches: [ "main", "release/*" ]
|
||||
pull_request:
|
||||
branches: ["main", "release/*"]
|
||||
branches: [ "main", "release/*" ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
|
119
.github/workflows/flutter_ci.yaml
vendored
119
.github/workflows/flutter_ci.yaml
vendored
|
@ -28,7 +28,7 @@ env:
|
|||
FLUTTER_VERSION: "3.22.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
CARGO_MAKE_VERSION: "0.37.18"
|
||||
CLOUD_VERSION: 0.6.34-amd64
|
||||
CLOUD_VERSION: 0.6.54-amd64
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
|
@ -40,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
|
||||
|
@ -74,9 +74,9 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ windows-2019 ]
|
||||
os: [windows-latest]
|
||||
include:
|
||||
- os: windows-2019
|
||||
- os: windows-latest
|
||||
flutter_profile: development-windows-x86
|
||||
target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
@ -101,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
|
||||
|
@ -123,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
|
||||
|
@ -217,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
|
||||
|
@ -262,11 +262,26 @@ jobs:
|
|||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Restarting with the correct version..."
|
||||
# Remove all containers if any exist
|
||||
if [ "$(docker ps -aq)" ]; then
|
||||
docker rm -f $(docker ps -aq)
|
||||
else
|
||||
echo "No containers to remove."
|
||||
fi
|
||||
|
||||
# Remove all volumes if any exist
|
||||
if [ "$(docker volume ls -q)" ]; then
|
||||
docker volume rm $(docker volume ls -q)
|
||||
else
|
||||
echo "No volumes to remove."
|
||||
fi
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
docker ps -a
|
||||
docker compose logs
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
|
@ -324,14 +339,14 @@ jobs:
|
|||
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 ]
|
||||
integration_test:
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
test_number: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
|
@ -340,82 +355,10 @@ jobs:
|
|||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Flutter Integration Test 1
|
||||
- name: Flutter Integration Test ${{ matrix.test_number }}
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
test_path: integration_test/desktop_runner_1.dart
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_2:
|
||||
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 2
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
test_path: integration_test/desktop_runner_2.dart
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_3:
|
||||
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 3
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
test_path: integration_test/desktop_runner_3.dart
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
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
|
||||
test_path: integration_test/desktop_runner_${{ matrix.test_number }}.dart
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
|
|
38
.github/workflows/ios_ci.yaml
vendored
38
.github/workflows/ios_ci.yaml
vendored
|
@ -20,7 +20,7 @@ on:
|
|||
- "!frontend/appflowy_web_app/**"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
FLUTTER_VERSION: "3.22.3"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
|
||||
concurrency:
|
||||
|
@ -48,13 +48,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
|
||||
|
@ -85,7 +85,7 @@ jobs:
|
|||
working-directory: frontend
|
||||
run: |
|
||||
rustup target install aarch64-apple-ios-sim
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
cargo install cargo-lipo
|
||||
cargo make appflowy-flutter-deps-tools
|
||||
shell: bash
|
||||
|
@ -102,16 +102,20 @@ jobs:
|
|||
model: "iPhone 15"
|
||||
shutdown_after_job: false
|
||||
|
||||
# - name: Run AppFlowy on simulator
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
# run: |
|
||||
# flutter run -d ${{ steps.simulator-action.outputs.udid }} &
|
||||
# pid=$!
|
||||
# sleep 500
|
||||
# kill $pid
|
||||
# continue-on-error: true
|
||||
- name: Run AppFlowy on simulator
|
||||
working-directory: frontend/appflowy_flutter
|
||||
run: |
|
||||
flutter run -d ${{ steps.simulator-action.outputs.udid }} &
|
||||
pid=$!
|
||||
sleep 500
|
||||
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 }}
|
||||
# Integration tests
|
||||
- name: Run integration tests
|
||||
working-directory: frontend/appflowy_flutter
|
||||
# The integration tests are flaky and sometimes fail with "Connection timed out":
|
||||
# Don't block the CI. If the tests fail, the CI will still pass.
|
||||
# Instead, we're using Code Magic to re-run the tests to check if they pass.
|
||||
continue-on-error: true
|
||||
run: flutter test integration_test/runner.dart -d ${{ steps.simulator-action.outputs.udid }}
|
||||
|
|
83
.github/workflows/mobile_ci.yml
vendored
Normal file
83
.github/workflows/mobile_ci.yml
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
name: Mobile-CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: "Branch to build"
|
||||
required: true
|
||||
default: "main"
|
||||
workflow_id:
|
||||
description: "Codemagic workflow ID"
|
||||
required: true
|
||||
default: "ios-workflow"
|
||||
type: choice
|
||||
options:
|
||||
- ios-workflow
|
||||
- android-workflow
|
||||
|
||||
env:
|
||||
CODEMAGIC_API_TOKEN: ${{ secrets.CODEMAGIC_API_TOKEN }}
|
||||
APP_ID: "6731d2f427e7c816080c3674"
|
||||
|
||||
jobs:
|
||||
trigger-mobile-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Codemagic Build
|
||||
id: trigger_build
|
||||
run: |
|
||||
RESPONSE=$(curl -X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "x-auth-token: $CODEMAGIC_API_TOKEN" \
|
||||
--data '{
|
||||
"appId": "${{ env.APP_ID }}",
|
||||
"workflowId": "${{ github.event.inputs.workflow_id }}",
|
||||
"branch": "${{ github.event.inputs.branch }}"
|
||||
}' \
|
||||
https://api.codemagic.io/builds)
|
||||
|
||||
BUILD_ID=$(echo $RESPONSE | jq -r '.buildId')
|
||||
echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT
|
||||
echo "build_id=$BUILD_ID"
|
||||
|
||||
- name: Wait for build and check status
|
||||
id: check_status
|
||||
run: |
|
||||
while true; do
|
||||
curl -X GET \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "x-auth-token: $CODEMAGIC_API_TOKEN" \
|
||||
https://api.codemagic.io/builds/${{ steps.trigger_build.outputs.build_id }} > /tmp/response.json
|
||||
|
||||
RESPONSE_WITHOUT_COMMAND=$(cat /tmp/response.json | jq 'walk(if type == "object" and has("subactions") then .subactions |= map(del(.command)) else . end)')
|
||||
STATUS=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.build.status')
|
||||
|
||||
if [ "$STATUS" = "finished" ]; then
|
||||
SUCCESS=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.success')
|
||||
BUILD_URL=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.buildUrl')
|
||||
echo "status=$STATUS" >> $GITHUB_OUTPUT
|
||||
echo "success=$SUCCESS" >> $GITHUB_OUTPUT
|
||||
echo "build_url=$BUILD_URL" >> $GITHUB_OUTPUT
|
||||
break
|
||||
elif [ "$STATUS" = "failed" ]; then
|
||||
echo "status=failed" >> $GITHUB_OUTPUT
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 60
|
||||
done
|
||||
|
||||
- name: Slack Notification
|
||||
uses: 8398a7/action-slack@v3
|
||||
if: always()
|
||||
with:
|
||||
status: ${{ steps.check_status.outputs.success == 'true' && 'success' || 'failure' }}
|
||||
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
|
||||
text: |
|
||||
Mobile CI Build Result
|
||||
Branch: ${{ github.event.inputs.branch }}
|
||||
Workflow: ${{ github.event.inputs.workflow_id }}
|
||||
Build URL: ${{ steps.check_status.outputs.build_url }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.RELEASE_SLACK_WEBHOOK }}
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
@ -7,7 +7,7 @@ on:
|
|||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
|
@ -74,7 +74,7 @@ jobs:
|
|||
run: |
|
||||
vcpkg integrate install
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
|
||||
- name: Build Windows app
|
||||
working-directory: frontend
|
||||
|
@ -159,7 +159,7 @@ jobs:
|
|||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
|
@ -257,7 +257,7 @@ jobs:
|
|||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
|
@ -371,7 +371,7 @@ jobs:
|
|||
sudo apt-get install -y alien libnotify-dev
|
||||
source $HOME/.cargo/env
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
rustup target add ${{ matrix.job.target }}
|
||||
|
||||
- name: Install gcc-aarch64-linux-gnu
|
||||
|
|
115
.github/workflows/rust_ci.yaml
vendored
115
.github/workflows/rust_ci.yaml
vendored
|
@ -15,88 +15,14 @@ on:
|
|||
- "main"
|
||||
- "develop"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CLOUD_VERSION: 0.6.34-amd64
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
CLOUD_VERSION: 0.8.3-amd64
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
|
||||
jobs:
|
||||
self-hosted-job:
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Set timezone for action
|
||||
uses: szenius/set-timezone@v2.0
|
||||
with:
|
||||
timezoneLinux: "US/Pacific"
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout Appflowy Cloud
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AppFlowy-IO/AppFlowy-Cloud
|
||||
path: AppFlowy-Cloud
|
||||
|
||||
- name: Prepare Appflowy Cloud env
|
||||
working-directory: AppFlowy-Cloud
|
||||
run: |
|
||||
cp deploy.env .env
|
||||
sed -i '' 's|RUST_LOG=.*|RUST_LOG=trace|' .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: ${{ 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
|
||||
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Run rust-lib tests
|
||||
working-directory: frontend/rust-lib
|
||||
env:
|
||||
RUST_LOG: info
|
||||
RUST_BACKTRACE: 1
|
||||
af_cloud_test_base_url: http://localhost
|
||||
af_cloud_test_ws_url: ws://localhost/ws/v1
|
||||
af_cloud_test_gotrue_url: http://localhost/gotrue
|
||||
run: |
|
||||
DISABLE_CI_TEST_LOG="true" cargo test --no-default-features --features="dart"
|
||||
|
||||
- name: rustfmt rust-lib
|
||||
run: cargo fmt --all -- --check
|
||||
working-directory: frontend/rust-lib/
|
||||
|
||||
- name: clippy rust-lib
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
working-directory: frontend/rust-lib
|
||||
|
||||
ubuntu-job:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set timezone for action
|
||||
|
@ -140,7 +66,7 @@ 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|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
|
||||
|
@ -150,26 +76,27 @@ jobs:
|
|||
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
|
||||
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
# Remove all containers if any exist
|
||||
if [ "$(docker ps -aq)" ]; then
|
||||
docker rm -f $(docker ps -aq)
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
echo "No containers to remove."
|
||||
fi
|
||||
|
||||
# Remove all volumes if any exist
|
||||
if [ "$(docker volume ls -q)" ]; then
|
||||
docker volume rm $(docker volume ls -q)
|
||||
else
|
||||
echo "No volumes to remove."
|
||||
fi
|
||||
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
docker ps -a
|
||||
docker compose logs
|
||||
|
||||
- name: Run rust-lib tests
|
||||
working-directory: frontend/rust-lib
|
||||
env:
|
||||
|
|
4
.github/workflows/rust_coverage.yml
vendored
4
.github/workflows/rust_coverage.yml
vendored
|
@ -11,7 +11,7 @@ on:
|
|||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
|
@ -41,7 +41,7 @@ jobs:
|
|||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
|
|
32
.github/workflows/tauri2_ci.yaml
vendored
32
.github/workflows/tauri2_ci.yaml
vendored
|
@ -5,13 +5,14 @@ on:
|
|||
paths:
|
||||
- ".github/workflows/tauri2_ci.yaml"
|
||||
- "frontend/rust-lib/**"
|
||||
paths-ignore:
|
||||
- "frontend/appflowy_web_app/**"
|
||||
- "frontend/resources/**"
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
CARGO_MAKE_VERSION: "0.36.6"
|
||||
CI: true
|
||||
|
||||
|
@ -20,34 +21,7 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# tauri-build-self-hosted:
|
||||
# if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
# runs-on: self-hosted
|
||||
#
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: install frontend dependencies
|
||||
# working-directory: frontend/appflowy_web_app
|
||||
# run: |
|
||||
# mkdir dist
|
||||
# pnpm install
|
||||
# cd src-tauri && cargo build
|
||||
#
|
||||
# - name: test and lint
|
||||
# working-directory: frontend/appflowy_web_app
|
||||
# run: |
|
||||
# pnpm run lint:tauri
|
||||
#
|
||||
# - uses: tauri-apps/tauri-action@v0
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# tauriScript: pnpm tauri
|
||||
# projectPath: frontend/appflowy_web_app
|
||||
# args: "--debug"
|
||||
|
||||
tauri-build-ubuntu:
|
||||
#if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
|
@ -121,4 +95,4 @@ jobs:
|
|||
with:
|
||||
tauriScript: pnpm tauri
|
||||
projectPath: frontend/appflowy_web_app
|
||||
args: "--debug"
|
||||
args: "--debug"
|
||||
|
|
8
.github/workflows/tauri_ci.yaml
vendored
8
.github/workflows/tauri_ci.yaml
vendored
|
@ -7,7 +7,7 @@ on:
|
|||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
|
@ -19,7 +19,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-20.04 ]
|
||||
platform: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
|
@ -73,7 +73,7 @@ jobs:
|
|||
if: matrix.platform == 'windows-latest'
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
vcpkg integrate install
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
|
@ -108,4 +108,4 @@ jobs:
|
|||
with:
|
||||
tauriScript: pnpm tauri
|
||||
projectPath: frontend/appflowy_tauri
|
||||
args: "--debug"
|
||||
args: "--debug"
|
||||
|
|
13
.github/workflows/tauri_release.yml
vendored
13
.github/workflows/tauri_release.yml
vendored
|
@ -4,20 +4,19 @@ on:
|
|||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'The branch to release'
|
||||
description: "The branch to release"
|
||||
required: true
|
||||
default: 'main'
|
||||
default: "main"
|
||||
version:
|
||||
description: 'The version to release'
|
||||
description: "The version to release"
|
||||
required: true
|
||||
default: '0.0.0'
|
||||
default: "0.0.0"
|
||||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
|
||||
jobs:
|
||||
|
||||
publish-tauri:
|
||||
permissions:
|
||||
contents: write
|
||||
|
@ -84,7 +83,7 @@ jobs:
|
|||
if: matrix.settings.platform == 'windows-latest'
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force duckscript_cli
|
||||
cargo install --force --locked duckscript_cli
|
||||
vcpkg integrate install
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
|
|
84
CHANGELOG.md
84
CHANGELOG.md
|
@ -1,4 +1,84 @@
|
|||
# Release Notes
|
||||
## Version 0.7.7 - 09/12/2024
|
||||
### New Features
|
||||
- Added support "set to page width" and "distribute columns evenly" for table block
|
||||
- Added support for reordering column/row in table block
|
||||
- Added support inserting multiple lines in table block
|
||||
- Revamped the mention page interactions in AI chat page
|
||||
- Improve the AI referenced sources UI in AI chat page
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed an error when opening files in the database in local mode
|
||||
- Fixed arrow up/down navigation not working for selecting a language in code block
|
||||
- Fixed an issue where deleting multiple blocks using the drag button on the document page didn't work
|
||||
|
||||
## Version 0.7.7 - 09/12/2024
|
||||
### Bug Fixes
|
||||
- Fixed sidebar menu resize regression
|
||||
- Fixed AI chat loading issues
|
||||
- Fixed inability to open local files in database
|
||||
- Fixed mentions remaining in notifications after removal from document
|
||||
- Fixed event card closing when clicking on empty space
|
||||
- Fixed keyboard shortcut issues
|
||||
|
||||
## Version 0.7.6 - 03/12/2024
|
||||
### New Features
|
||||
- Revamped the simple table UI
|
||||
- Added support for capturing images from camera on mobile
|
||||
### Bug Fixes
|
||||
- Improved markdown rendering capabilities in AI writer
|
||||
- Fixed an issue where pressing Enter on a collapsed toggle list would add an unnecessary new line
|
||||
- Fixed an issue where creating a document from slash menu could insert content at incorrect position
|
||||
|
||||
## Version 0.7.5 - 25/11/2024
|
||||
### Bug Fixes
|
||||
- Improved chat response parsing
|
||||
- Fixed toggle list icon direction for RTL mode
|
||||
- Fixed cross blocks formatting not reflecting in float toolbar
|
||||
- Fixed unable to click inside the toggle list to create a new paragraph
|
||||
- Fixed open file error 50 on macOS
|
||||
- Fixed upload file exceed limit error
|
||||
|
||||
## Version 0.7.4 - 19/11/2024
|
||||
### New Features
|
||||
- Support uploading WebP and BMP images
|
||||
- Support managing workspaces on mobile
|
||||
- Support adding toggle headings on mobile
|
||||
- Improve the AI chat page UI
|
||||
### Bug Fixes
|
||||
- Optimized the workspace menu loading performance
|
||||
- Optimized tab switching performance
|
||||
- Fixed searching issues in Document page
|
||||
|
||||
## Version 0.7.3 - 07/11/2024
|
||||
### New Features
|
||||
- Enable custom URLs for published pages
|
||||
- Support toggling headings
|
||||
- Create a subpage by typing in the document
|
||||
- Turn selected blocks into a subpage
|
||||
- Add a manual date picker for the Date property
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed an issue where the workspace owner was unable to delete spaces created by others
|
||||
- Fixed cursor height inconsistencies with text height
|
||||
- Fixed editing issues in Kanban cards
|
||||
- Fixed an issue preventing images or files from being dropped into empty paragraphs
|
||||
|
||||
## 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
|
||||
|
@ -60,7 +140,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.
|
||||
|
@ -890,4 +970,4 @@ Bug fixes and improvements
|
|||
- Increased height of action
|
||||
- CPU performance issue
|
||||
- Fix potential data parser error
|
||||
- More foundation work for online collaboration
|
||||
- More foundation work for online collaboration
|
65
README.md
65
README.md
|
@ -42,10 +42,12 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo
|
|||
## User Installation
|
||||
|
||||
- [Download AppFlowy Desktop (macOS, Windows, and Linux)](https://github.com/AppFlowy-IO/AppFlowy/releases)
|
||||
- Other channels: [FlatHub](https://flathub.org/apps/io.appflowy.AppFlowy), [Snapcraft](https://snapcraft.io/appflowy), [Sourceforge](https://sourceforge.net/projects/appflowy/)
|
||||
- Other
|
||||
channels: [FlatHub](https://flathub.org/apps/io.appflowy.AppFlowy), [Snapcraft](https://snapcraft.io/appflowy), [Sourceforge](https://sourceforge.net/projects/appflowy/)
|
||||
- Available on
|
||||
- [App Store](https://apps.apple.com/app/appflowy/id6457261352): iPhone
|
||||
- [Play Store](https://play.google.com/store/apps/details?id=io.appflowy.appflowy): Android 10 or above; ARMv7 is not supported
|
||||
- [App Store](https://apps.apple.com/app/appflowy/id6457261352): iPhone
|
||||
- [Play Store](https://play.google.com/store/apps/details?id=io.appflowy.appflowy): Android 10 or above; ARMv7 is
|
||||
not supported
|
||||
- [Self-hosting AppFlowy](https://docs.appflowy.io/docs/guides/appflowy/self-hosting-appflowy)
|
||||
- [Source](https://docs.appflowy.io/docs/documentation/appflowy/from-source)
|
||||
|
||||
|
@ -61,15 +63,18 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo
|
|||
|
||||
## Getting Started with development
|
||||
|
||||
Please view the [documentation](https://docs.appflowy.io/docs/documentation/appflowy/from-source) for OS specific development instructions
|
||||
Please view the [documentation](https://docs.appflowy.io/docs/documentation/appflowy/from-source) for OS specific
|
||||
development instructions
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [AppFlowy Roadmap ReadMe](https://docs.appflowy.io/docs/appflowy/roadmap)
|
||||
- [AppFlowy Public Roadmap](https://github.com/orgs/AppFlowy-IO/projects/5/views/12)
|
||||
|
||||
If you'd like to propose a feature, submit a feature request [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=feature_request.yaml&title=%5BFR%5D+) <br/>
|
||||
If you'd like to report a bug, submit a bug report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+)
|
||||
If you'd like to propose a feature, submit a feature
|
||||
request [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=feature_request.yaml&title=%5BFR%5D+) <br/>
|
||||
If you'd like to report a bug, submit a bug
|
||||
report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+)
|
||||
|
||||
## **Releases**
|
||||
|
||||
|
@ -77,16 +82,24 @@ Please see the [changelog](https://www.appflowy.io/whatsnew) for more details ab
|
|||
|
||||
## Contributing
|
||||
|
||||
Contributions make the open-source community a fantastic place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. Please look at [Contributing to AppFlowy](https://docs.appflowy.io/docs/documentation/software-contributions/contributing-to-appflowy) for details.
|
||||
Contributions make the open-source community a fantastic place to learn, inspire, and create. Any contributions you make
|
||||
are **greatly appreciated**. Please look
|
||||
at [Contributing to AppFlowy](https://docs.appflowy.io/docs/documentation/software-contributions/contributing-to-appflowy)
|
||||
for details.
|
||||
|
||||
If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly easier to use or understand, **Congratulations!** If your administrative and managerial work behind the scenes sustains the community, **Congratulations!** You are now an official contributor to AppFlowy. Get in touch with us ([link](https://tally.so/r/mKP5z3)) to receive the very special Contributor T-shirt!
|
||||
If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly
|
||||
easier to use or understand, **Congratulations!** If your administrative and managerial work behind the scenes sustains
|
||||
the community, **Congratulations!** You are now an official contributor to AppFlowy. Get in touch with
|
||||
us ([link](https://tally.so/r/mKP5z3)) to receive the very special Contributor T-shirt!
|
||||
Proudly wear your T-shirt and show it to us by tagging [@appflowy](https://twitter.com/appflowy) on Twitter.
|
||||
|
||||
## Translations 🌎🗺
|
||||
|
||||
[](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy?ref=badge)
|
||||
|
||||
To add translations, you can manually edit the JSON translation files in `/frontend/resources/translations`, use the [inlang online editor](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy), or run `npx inlang machine translate` to add missing translations.
|
||||
To add translations, you can manually edit the JSON translation files in `/frontend/resources/translations`, use
|
||||
the [inlang online editor](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy), or
|
||||
run `npx inlang machine translate` to add missing translations.
|
||||
|
||||
## Join the community to build AppFlowy together
|
||||
|
||||
|
@ -96,16 +109,30 @@ To add translations, you can manually edit the JSON translation files in `/front
|
|||
|
||||
## Why Are We Building This?
|
||||
|
||||
Notion has been our favourite project and knowledge management tool in recent years because of its aesthetic appeal and functionality. Our team uses it daily, and we are on its paid plan. However, as we all know, Notion has its limitations. These include weak data security and poor compatibility with mobile devices. Likewise, alternative collaborative workplace management tools also have their constraints.
|
||||
Notion has been our favourite project and knowledge management tool in recent years because of its aesthetic appeal and
|
||||
functionality. Our team uses it daily, and we are on its paid plan. However, as we all know, Notion has its limitations.
|
||||
These include weak data security and poor compatibility with mobile devices. Likewise, alternative collaborative
|
||||
workplace management tools also have their constraints.
|
||||
|
||||
The limitations we encountered using these tools and our past work experience with collaborative productivity tools have led to our firm belief that there is a glass ceiling on what's possible for these tools in the future. This emanates from the fact that these tools will probably struggle to scale horizontally at some point and be forced to prioritize a proportion of customers whose needs differ from the rest. While decision-makers want a workplace OS, it is impossible to come up with a one-size fits all solution in such a fragmented market.
|
||||
The limitations we encountered using these tools and our past work experience with collaborative productivity tools have
|
||||
led to our firm belief that there is a glass ceiling on what's possible for these tools in the future. This emanates
|
||||
from the fact that these tools will probably struggle to scale horizontally at some point and be forced to prioritize a
|
||||
proportion of customers whose needs differ from the rest. While decision-makers want a workplace OS, it is impossible to
|
||||
come up with a one-size fits all solution in such a fragmented market.
|
||||
|
||||
When a customer's evolving core needs are not satisfied, they either switch to another or build one from the ground up, in-house. Consequently, they either go under another ceiling or buy an expensive ticket to learn a hard lesson. This is a requirement for many resources and expertise, building a reliable and easy-to-use collaborative tool, not to mention the speed and native experience. The same may apply to individual users as well.
|
||||
When a customer's evolving core needs are not satisfied, they either switch to another or build one from the ground up,
|
||||
in-house. Consequently, they either go under another ceiling or buy an expensive ticket to learn a hard lesson. This is
|
||||
a requirement for many resources and expertise, building a reliable and easy-to-use collaborative tool, not to mention
|
||||
the speed and native experience. The same may apply to individual users as well.
|
||||
|
||||
All these restrictions necessitate our mission - to make it possible for anyone to create apps that suit their needs well.
|
||||
All these restrictions necessitate our mission - to make it possible for anyone to create apps that suit their needs
|
||||
well.
|
||||
|
||||
- To individuals, we would like to offer Notion's functionality, data security, and cross-platform native experience.
|
||||
- To enterprises and hackers, AppFlowy is dedicated to offering building blocks and collaboration infra services to enable you to make apps on your own. Moreover, you have 100% control of your data. You can design and modify AppFlowy your way, with a single codebase written in Flutter and Rust supporting multiple platforms armed with long-term maintainability.
|
||||
- To enterprises and hackers, AppFlowy is dedicated to offering building blocks and collaboration infra services to
|
||||
enable you to make apps on your own. Moreover, you have 100% control of your data. You can design and modify AppFlowy
|
||||
your way, with a single codebase written in Flutter and Rust supporting multiple platforms armed with long-term
|
||||
maintainability.
|
||||
|
||||
We decided to achieve this mission by upholding the three most fundamental values:
|
||||
|
||||
|
@ -113,16 +140,20 @@ We decided to achieve this mission by upholding the three most fundamental value
|
|||
- Reliable native experience
|
||||
- Community-driven extensibility
|
||||
|
||||
We do not claim to outperform Notion in terms of functionality and design, at least for now. Besides, our priority doesn't lie in more functionality at the moment. Instead, we would like to cultivate a community to democratize the knowledge and wheels of making complex workplace management tools while enabling people and businesses to create beautiful things on their own by equipping them with a versatile toolbox of building blocks.
|
||||
We do not claim to outperform Notion in terms of functionality and design, at least for now. Besides, our priority
|
||||
doesn't lie in more functionality at the moment. Instead, we would like to cultivate a community to democratize the
|
||||
knowledge and wheels of making complex workplace management tools while enabling people and businesses to create
|
||||
beautiful things on their own by equipping them with a versatile toolbox of building blocks.
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information.
|
||||
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for
|
||||
more information.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Special thanks to these amazing projects which help power AppFlowy.IO:
|
||||
|
||||
- [flutter-quill](https://github.com/singerdmx/flutter-quill)
|
||||
- [cargo-make](https://github.com/sagiegurari/cargo-make)
|
||||
- [contrib.rocks](https://contrib.rocks)
|
||||
- [flutter_chat_ui](https://pub.dev/packages/flutter_chat_ui)
|
47
codemagic.yaml
Normal file
47
codemagic.yaml
Normal file
|
@ -0,0 +1,47 @@
|
|||
workflows:
|
||||
ios-workflow:
|
||||
name: iOS Workflow
|
||||
instance_type: mac_mini_m2
|
||||
max_build_duration: 30
|
||||
environment:
|
||||
flutter: 3.22.3
|
||||
xcode: latest
|
||||
cocoapods: default
|
||||
|
||||
scripts:
|
||||
- name: Build Flutter
|
||||
script: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
rustc --version
|
||||
cargo --version
|
||||
|
||||
cd frontend
|
||||
|
||||
rustup target install aarch64-apple-ios-sim
|
||||
cargo install --force cargo-make
|
||||
cargo install --force --locked duckscript_cli
|
||||
cargo install --force cargo-lipo
|
||||
|
||||
cargo make appflowy-flutter-deps-tools
|
||||
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
|
||||
cargo make --profile development-ios-arm64-sim code_generation
|
||||
|
||||
- name: iOS integration tests
|
||||
script: |
|
||||
cd frontend/appflowy_flutter
|
||||
flutter emulators --launch apple_ios_simulator
|
||||
flutter -d iPhone test integration_test/runner.dart
|
||||
|
||||
artifacts:
|
||||
- build/ios/ipa/*.ipa
|
||||
- /tmp/xcodebuild_logs/*.log
|
||||
- flutter_drive.log
|
||||
|
||||
publishing:
|
||||
email:
|
||||
recipients:
|
||||
- lucas.xu@appflowy.io
|
||||
notify:
|
||||
success: true
|
||||
failure: true
|
|
@ -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.1"
|
||||
APPFLOWY_VERSION = "0.7.8"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="appflowy-flutter" />
|
||||
<!-- <data android:host="login-callback" /> -->
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!--
|
||||
|
@ -50,7 +49,8 @@
|
|||
<!-- Permission to read files from external storage (outside application container).
|
||||
As of Android 12 this permission no longer has any effect. Instead use the
|
||||
READ_MEDIA_IMAGES, READ_MEDIA_VIDEO or READM_MEDIA_AUDIO permissions. -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<!-- Permissions to read media files. -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<queries>
|
||||
|
@ -65,4 +65,5 @@
|
|||
-->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
</manifest>
|
|
@ -4,7 +4,6 @@ import 'package:appflowy/plugins/database/board/presentation/widgets/board_colum
|
|||
import 'package:appflowy/plugins/database/board/presentation/widgets/board_hidden_groups.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/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
@ -121,22 +120,3 @@ void main() {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension FlowySvgFinder on CommonFinders {
|
||||
Finder byFlowySvg(FlowySvgData svg) => _FlowySvgFinder(svg);
|
||||
}
|
||||
|
||||
class _FlowySvgFinder extends MatchFinder {
|
||||
_FlowySvgFinder(this.svg);
|
||||
|
||||
final FlowySvgData svg;
|
||||
|
||||
@override
|
||||
String get description => 'flowy_svg "$svg"';
|
||||
|
||||
@override
|
||||
bool matches(Element candidate) {
|
||||
final Widget widget = candidate.widget;
|
||||
return widget is FlowySvg && widget.svg == svg;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ 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:time/time.dart';
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
@ -14,6 +15,31 @@ void main() {
|
|||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('board row test', () {
|
||||
testWidgets('edit item in ToDo card', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||
const name = 'Card 1';
|
||||
final card1 = find.ancestor(
|
||||
matching: find.byType(RowCard),
|
||||
of: find.text(name),
|
||||
);
|
||||
await tester.hoverOnWidget(
|
||||
card1,
|
||||
onHover: () async {
|
||||
final editCard = find.byType(EditCardAccessory);
|
||||
await tester.tapButton(editCard);
|
||||
},
|
||||
);
|
||||
await tester.showKeyboard(card1);
|
||||
tester.testTextInput.enterText("");
|
||||
await tester.pump(300.milliseconds);
|
||||
tester.testTextInput.enterText("a");
|
||||
await tester.pump(300.milliseconds);
|
||||
expect(find.text('a'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('delete item in ToDo card', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import 'data_migration/data_migration_test_runner.dart'
|
||||
as data_migration_test_runner;
|
||||
import 'document/document_test_runner.dart' as document_test_runner;
|
||||
import 'set_env.dart' as preset_af_cloud_env_test;
|
||||
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
|
||||
import 'sidebar/sidebar_rename_untitled_test.dart'
|
||||
as sidebar_rename_untitled_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();
|
||||
|
||||
|
@ -22,4 +25,5 @@ Future<void> main() async {
|
|||
|
||||
// sidebar
|
||||
sidebar_move_page_test.main();
|
||||
sidebar_rename_untitled_test.main();
|
||||
}
|
||||
|
|
|
@ -1,33 +1,10 @@
|
|||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
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/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/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.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:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
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();
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.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('AI Writer:', () {
|
||||
testWidgets('the ai writer transaction should only apply in memory',
|
||||
(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,
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_aiWriter.tr(),
|
||||
);
|
||||
expect(find.byType(AIWriterBlockComponent), findsOneWidget);
|
||||
|
||||
// switch to another page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
// switch back to the page
|
||||
await tester.openPage(pageName);
|
||||
|
||||
// expect the ai writer block is not in the document
|
||||
expect(find.byType(AIWriterBlockComponent), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
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';
|
||||
|
@ -99,6 +102,27 @@ void main() {
|
|||
// 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',
|
||||
|
@ -168,6 +192,84 @@ void main() {
|
|||
),
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
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/plugins/shared/share/publish_tab.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_menu.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/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();
|
||||
|
||||
group('Publish:', () {
|
||||
testWidgets('publish document', (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,
|
||||
);
|
||||
|
||||
// open the publish menu
|
||||
await tester.openPublishMenu();
|
||||
|
||||
// publish the document
|
||||
final publishButton = find.byType(PublishButton);
|
||||
final unpublishButton = find.byType(UnPublishButton);
|
||||
await tester.tapButton(publishButton);
|
||||
|
||||
// expect to see unpublish, visit site and manage all sites button
|
||||
expect(unpublishButton, findsOneWidget);
|
||||
expect(find.text(LocaleKeys.shareAction_visitSite.tr()), findsOneWidget);
|
||||
|
||||
// unpublish the document
|
||||
await tester.tapButton(unpublishButton);
|
||||
|
||||
// expect to see publish button
|
||||
expect(publishButton, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('rename path name', (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,
|
||||
);
|
||||
|
||||
// open the publish menu
|
||||
await tester.openPublishMenu();
|
||||
|
||||
// publish the document
|
||||
final publishButton = find.byType(PublishButton);
|
||||
await tester.tapButton(publishButton);
|
||||
|
||||
// rename the path name
|
||||
final inputField = find.descendant(
|
||||
of: find.byType(ShareMenu),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
|
||||
// rename with invalid name
|
||||
await tester.tap(inputField);
|
||||
await tester.enterText(inputField, '&&&&????');
|
||||
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to see the toast with error message
|
||||
final errorToast1 = find.text(
|
||||
LocaleKeys.settings_sites_error_publishNameContainsInvalidCharacters
|
||||
.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(errorToast1);
|
||||
await tester.pumpUntilNotFound(errorToast1);
|
||||
|
||||
// rename with long name
|
||||
await tester.tap(inputField);
|
||||
await tester.enterText(inputField, 'long-path-name' * 200);
|
||||
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to see the toast with error message
|
||||
final errorToast2 = find.text(
|
||||
LocaleKeys.settings_sites_error_publishNameTooLong.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(errorToast2);
|
||||
await tester.pumpUntilNotFound(errorToast2);
|
||||
|
||||
// rename with empty name
|
||||
await tester.tap(inputField);
|
||||
await tester.enterText(inputField, '');
|
||||
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to see the toast with error message
|
||||
final errorToast3 = find.text(
|
||||
LocaleKeys.settings_sites_error_publishNameCannotBeEmpty.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(errorToast3);
|
||||
await tester.pumpUntilNotFound(errorToast3);
|
||||
|
||||
// input the new path name
|
||||
await tester.tap(inputField);
|
||||
await tester.enterText(inputField, 'new-path-name');
|
||||
// click save button
|
||||
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to see the toast with success message
|
||||
final successToast = find.text(
|
||||
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(successToast);
|
||||
await tester.pumpUntilNotFound(successToast);
|
||||
|
||||
// click the copy link button
|
||||
await tester.tapButton(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowySvg &&
|
||||
widget.svg.path == FlowySvgs.m_toolbar_link_m.path,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
// check the clipboard has the link
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text?.contains('new-path-name'),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('re-publish the document', (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,
|
||||
);
|
||||
|
||||
// open the publish menu
|
||||
await tester.openPublishMenu();
|
||||
|
||||
// publish the document
|
||||
final publishButton = find.byType(PublishButton);
|
||||
await tester.tapButton(publishButton);
|
||||
|
||||
// rename the path name
|
||||
final inputField = find.descendant(
|
||||
of: find.byType(ShareMenu),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
|
||||
// input the new path name
|
||||
const newName = 'new-path-name';
|
||||
await tester.enterText(inputField, newName);
|
||||
// click save button
|
||||
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to see the toast with success message
|
||||
final successToast = find.text(
|
||||
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
|
||||
);
|
||||
await tester.pumpUntilNotFound(successToast);
|
||||
|
||||
// unpublish the document
|
||||
final unpublishButton = find.byType(UnPublishButton);
|
||||
await tester.tapButton(unpublishButton);
|
||||
|
||||
final unpublishSuccessToast = find.text(
|
||||
LocaleKeys.publish_unpublishSuccessfully.tr(),
|
||||
);
|
||||
await tester.pumpUntilNotFound(unpublishSuccessToast);
|
||||
|
||||
// re-publish the document
|
||||
await tester.tapButton(publishButton);
|
||||
|
||||
// expect to see the toast with success message
|
||||
final rePublishSuccessToast = find.text(
|
||||
LocaleKeys.publish_publishSuccessfully.tr(),
|
||||
);
|
||||
await tester.pumpUntilNotFound(rePublishSuccessToast);
|
||||
|
||||
// check the clipboard has the link
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text?.contains(newName),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_ai_writer_test.dart' as document_ai_writer_test;
|
||||
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;
|
||||
import 'document_publish_test.dart' as document_publish_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
document_option_actions_test.main();
|
||||
document_copy_link_to_block_test.main();
|
||||
document_publish_test.main();
|
||||
document_ai_writer_test.main();
|
||||
}
|
||||
|
|
|
@ -1,34 +1,13 @@
|
|||
// 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_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/gestures.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';
|
||||
|
||||
void main() {
|
||||
|
@ -92,6 +71,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,
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_input.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();
|
||||
|
||||
testWidgets('Rename empty name view (untitled)', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// click the ... button and open rename dialog
|
||||
await tester.hoverOnPageName(
|
||||
ViewLayoutPB.Document.defaultName,
|
||||
onHover: () async {
|
||||
await tester.tapPageOptionButton();
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.disclosureAction_rename.tr(),
|
||||
);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(NavigatorTextFieldDialog), findsOneWidget);
|
||||
|
||||
final textField = tester.widget<FlowyFormTextInput>(
|
||||
find.descendant(
|
||||
of: find.byType(NavigatorTextFieldDialog),
|
||||
matching: find.byType(FlowyFormTextInput),
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
textField.controller!.text,
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
);
|
||||
});
|
||||
}
|
|
@ -1,21 +1,12 @@
|
|||
// ignore_for_file: unused_import
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.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/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.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/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
@ -1,24 +1,9 @@
|
|||
// 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/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/settings/pages/settings_account_view.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/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
@ -1,33 +1,11 @@
|
|||
// 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/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/settings/pages/account/account_user_profile.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.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:flowy_infra_ui/style_widget/text_field.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/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();
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
// ignore_for_file: unused_import
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.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: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/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.dart';
|
||||
|
||||
|
@ -49,6 +40,10 @@ void main() {
|
|||
await tester.changeWorkspaceIcon(icon);
|
||||
await tester.changeWorkspaceName(name);
|
||||
|
||||
await tester.pumpUntilNotFound(
|
||||
find.text(LocaleKeys.workspace_renameSuccess.tr()),
|
||||
);
|
||||
|
||||
workspaceIcon = tester.widget<WorkspaceIcon>(
|
||||
find.byType(WorkspaceIcon),
|
||||
);
|
||||
|
|
|
@ -1,38 +1,19 @@
|
|||
// 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_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 '../../../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();
|
||||
|
||||
group('collaborative workspace: ', () {
|
||||
group('collaborative workspace:', () {
|
||||
// combine the create and delete workspace test to reduce the time
|
||||
testWidgets('create a new workspace, open it and then delete it',
|
||||
(tester) async {
|
||||
|
@ -93,5 +74,79 @@ void main() {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('check the member count immediately after creating a workspace',
|
||||
(tester) async {
|
||||
// only run the test when the feature flag is on
|
||||
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const name = 'AppFlowy.IO';
|
||||
// the workspace will be opened after created
|
||||
await tester.createCollaborativeWorkspace(name);
|
||||
|
||||
final loading = find.byType(Loading);
|
||||
await tester.pumpUntilNotFound(loading);
|
||||
|
||||
await tester.openCollaborativeWorkspaceMenu();
|
||||
|
||||
// expect to see the member count
|
||||
final memberCount = find.text('1 member');
|
||||
expect(memberCount, findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('only display one menu item in the workspace menu',
|
||||
(tester) async {
|
||||
// only run the test when the feature flag is on
|
||||
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const name = 'AppFlowy.IO';
|
||||
// the workspace will be opened after created
|
||||
await tester.createCollaborativeWorkspace(name);
|
||||
|
||||
final loading = find.byType(Loading);
|
||||
await tester.pumpUntilNotFound(loading);
|
||||
|
||||
await tester.openCollaborativeWorkspaceMenu();
|
||||
|
||||
// hover on the workspace and click the more button
|
||||
final workspaceItem = find.byWidgetPredicate(
|
||||
(w) => w is WorkspaceMenuItem && w.workspace.name == name,
|
||||
);
|
||||
await tester.hoverOnWidget(
|
||||
workspaceItem,
|
||||
onHover: () async {
|
||||
final moreButton = find.byWidgetPredicate(
|
||||
(w) => w is WorkspaceMoreActionList && w.workspace.name == name,
|
||||
);
|
||||
expect(moreButton, findsOneWidget);
|
||||
await tester.tapButton(moreButton);
|
||||
|
||||
// click it again
|
||||
await tester.tapButton(moreButton);
|
||||
|
||||
// nothing should happen
|
||||
expect(
|
||||
find.text(LocaleKeys.button_rename.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,28 +1,17 @@
|
|||
// 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/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/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();
|
||||
|
@ -61,11 +50,11 @@ void main() {
|
|||
final plainText = clipboardContent.plainText;
|
||||
expect(
|
||||
plainText,
|
||||
startsWith(ShareConstants.shareBaseUrl),
|
||||
matches(appflowySharePageLinkPattern),
|
||||
);
|
||||
|
||||
final shareValues = plainText!
|
||||
.replaceAll('${ShareConstants.shareBaseUrl}/', '')
|
||||
.replaceAll('https://${ShareConstants.shareBaseUrl}/', '')
|
||||
.split('/');
|
||||
final workspaceId = shareValues[0];
|
||||
expect(workspaceId, isNotEmpty);
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.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/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Tabs', () {
|
||||
testWidgets('close other tabs before opening a new workspace',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const name = 'AppFlowy.IO';
|
||||
// the workspace will be opened after created
|
||||
await tester.createCollaborativeWorkspace(name);
|
||||
|
||||
final loading = find.byType(Loading);
|
||||
await tester.pumpUntilNotFound(loading);
|
||||
|
||||
// create new tabs in the workspace
|
||||
expect(find.byType(FlowyTab), findsNothing);
|
||||
|
||||
const documentOneName = 'document one';
|
||||
const documentTwoName = 'document two';
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: documentOneName,
|
||||
);
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: documentTwoName,
|
||||
);
|
||||
|
||||
/// Open second menu item in a new tab
|
||||
await tester.openAppInNewTab(documentOneName, ViewLayoutPB.Document);
|
||||
|
||||
/// Open third menu item in a new tab
|
||||
await tester.openAppInNewTab(documentTwoName, ViewLayoutPB.Document);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// switch to the another workspace
|
||||
final Finder items = find.byType(WorkspaceMenuItem);
|
||||
await tester.openCollaborativeWorkspaceMenu();
|
||||
await tester.pumpUntilFound(items);
|
||||
expect(items, findsNWidgets(2));
|
||||
|
||||
// open the first workspace
|
||||
await tester.tap(items.first);
|
||||
await tester.pumpUntilNotFound(loading);
|
||||
|
||||
expect(find.byType(FlowyTab), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,35 +1,11 @@
|
|||
// 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';
|
||||
|
||||
|
|
|
@ -1,36 +1,23 @@
|
|||
// 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/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/plugins/shared/share/publish_tab.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_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_settings_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.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:flowy_infra_ui/flowy_infra_ui.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/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -93,4 +80,266 @@ void main() {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('sites settings:', () {
|
||||
testWidgets(
|
||||
'manage published page, set it as homepage, remove the homepage',
|
||||
(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,
|
||||
);
|
||||
|
||||
// open the publish menu
|
||||
await tester.openPublishMenu();
|
||||
|
||||
// publish the document
|
||||
await tester.tapButton(find.byType(PublishButton));
|
||||
|
||||
// click empty area to close the publish menu
|
||||
await tester.tapAt(Offset.zero);
|
||||
await tester.pumpAndSettle();
|
||||
// check if the page is published in sites page
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.sites);
|
||||
// wait the backend return the sites data
|
||||
await tester.wait(1000);
|
||||
|
||||
// check if the page is published in sites page
|
||||
final pageItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is PublishedViewItem &&
|
||||
widget.publishInfoView.view.name == pageName,
|
||||
);
|
||||
expect(pageItem, findsOneWidget);
|
||||
|
||||
// comment it out because it's not allowed to update the namespace in free plan
|
||||
// // set it to homepage
|
||||
// await tester.tapButton(
|
||||
// find.textContaining(
|
||||
// LocaleKeys.settings_sites_selectHomePage.tr(),
|
||||
// ),
|
||||
// );
|
||||
// await tester.tapButton(
|
||||
// find.descendant(
|
||||
// of: find.byType(SelectHomePageMenu),
|
||||
// matching: find.text(pageName),
|
||||
// ),
|
||||
// );
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
// // check if the page is set to homepage
|
||||
// final homePageItem = find.descendant(
|
||||
// of: find.byType(DomainItem),
|
||||
// matching: find.text(pageName),
|
||||
// );
|
||||
// expect(homePageItem, findsOneWidget);
|
||||
|
||||
// // remove the homepage
|
||||
// await tester.tapButton(find.byType(DomainMoreAction));
|
||||
// await tester.tapButton(
|
||||
// find.text(LocaleKeys.settings_sites_removeHomepage.tr()),
|
||||
// );
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
// // check if the page is removed from homepage
|
||||
// expect(homePageItem, findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('update namespace', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// check if the page is published in sites page
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.sites);
|
||||
// wait the backend return the sites data
|
||||
await tester.wait(1000);
|
||||
|
||||
// update the domain
|
||||
final domainMoreAction = find.byType(DomainMoreAction);
|
||||
await tester.tapButton(domainMoreAction);
|
||||
final updateNamespaceButton = find.text(
|
||||
LocaleKeys.settings_sites_updateNamespace.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(updateNamespaceButton);
|
||||
|
||||
// click the update namespace button
|
||||
|
||||
await tester.tapButton(updateNamespaceButton);
|
||||
|
||||
// comment it out because it's not allowed to update the namespace in free plan
|
||||
// expect to see the dialog
|
||||
// await tester.updateNamespace('&&&???');
|
||||
|
||||
// // need to upgrade to pro plan to update the namespace
|
||||
// final errorToast = find.text(
|
||||
// LocaleKeys.settings_sites_error_proPlanLimitation.tr(),
|
||||
// );
|
||||
// await tester.pumpUntilFound(errorToast);
|
||||
// expect(errorToast, findsOneWidget);
|
||||
// await tester.pumpUntilNotFound(errorToast);
|
||||
|
||||
// comment it out because it's not allowed to update the namespace in free plan
|
||||
// // short namespace
|
||||
// await tester.updateNamespace('a');
|
||||
|
||||
// // expect to see the toast with error message
|
||||
// final errorToast2 = find.text(
|
||||
// LocaleKeys.settings_sites_error_namespaceTooShort.tr(),
|
||||
// );
|
||||
// await tester.pumpUntilFound(errorToast2);
|
||||
// expect(errorToast2, findsOneWidget);
|
||||
// await tester.pumpUntilNotFound(errorToast2);
|
||||
// // valid namespace
|
||||
// await tester.updateNamespace('AppFlowy');
|
||||
|
||||
// // expect to see the toast with success message
|
||||
// final successToast = find.text(
|
||||
// LocaleKeys.settings_sites_success_namespaceUpdated.tr(),
|
||||
// );
|
||||
// await tester.pumpUntilFound(successToast);
|
||||
// expect(successToast, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('''
|
||||
More actions for published page:
|
||||
1. visit site
|
||||
2. copy link
|
||||
3. settings
|
||||
4. unpublish
|
||||
5. custom url
|
||||
''', (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,
|
||||
);
|
||||
|
||||
// open the publish menu
|
||||
await tester.openPublishMenu();
|
||||
|
||||
// publish the document
|
||||
await tester.tapButton(find.byType(PublishButton));
|
||||
|
||||
// click empty area to close the publish menu
|
||||
await tester.tapAt(Offset.zero);
|
||||
await tester.pumpAndSettle();
|
||||
// check if the page is published in sites page
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.sites);
|
||||
// wait the backend return the sites data
|
||||
await tester.wait(1000);
|
||||
|
||||
// check if the page is published in sites page
|
||||
final pageItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is PublishedViewItem &&
|
||||
widget.publishInfoView.view.name == pageName,
|
||||
);
|
||||
expect(pageItem, findsOneWidget);
|
||||
|
||||
final copyLinkItem = find.text(LocaleKeys.shareAction_copyLink.tr());
|
||||
final customUrlItem = find.text(LocaleKeys.settings_sites_customUrl.tr());
|
||||
final unpublishItem = find.text(LocaleKeys.shareAction_unPublish.tr());
|
||||
|
||||
// custom url
|
||||
final publishMoreAction = find.byType(PublishedViewMoreAction);
|
||||
|
||||
// click the copy link button
|
||||
{
|
||||
await tester.tapButton(publishMoreAction);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilFound(copyLinkItem);
|
||||
await tester.tapButton(copyLinkItem);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilNotFound(copyLinkItem);
|
||||
|
||||
final clipboardContent = await getIt<ClipboardService>().getData();
|
||||
final plainText = clipboardContent.plainText;
|
||||
expect(
|
||||
plainText,
|
||||
contains(pageName),
|
||||
);
|
||||
}
|
||||
|
||||
// custom url
|
||||
{
|
||||
await tester.tapButton(publishMoreAction);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilFound(customUrlItem);
|
||||
await tester.tapButton(customUrlItem);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilNotFound(customUrlItem);
|
||||
|
||||
// see the custom url dialog
|
||||
final customUrlDialog = find.byType(PublishedViewSettingsDialog);
|
||||
expect(customUrlDialog, findsOneWidget);
|
||||
|
||||
// rename the custom url
|
||||
final textField = find.descendant(
|
||||
of: customUrlDialog,
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await tester.enterText(textField, 'hello-world');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the save button
|
||||
final saveButton = find.descendant(
|
||||
of: customUrlDialog,
|
||||
matching: find.text(LocaleKeys.button_save.tr()),
|
||||
);
|
||||
await tester.tapButton(saveButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to see the toast with success message
|
||||
final successToast = find.text(
|
||||
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(successToast);
|
||||
expect(successToast, findsOneWidget);
|
||||
}
|
||||
|
||||
// unpublish
|
||||
{
|
||||
await tester.tapButton(publishMoreAction);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilFound(unpublishItem);
|
||||
await tester.tapButton(unpublishItem);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilNotFound(unpublishItem);
|
||||
|
||||
// expect to see the toast with success message
|
||||
final successToast = find.text(
|
||||
LocaleKeys.publish_unpublishSuccessfully.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(successToast);
|
||||
expect(successToast, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(successToast);
|
||||
|
||||
// check if the page is unpublished in sites page
|
||||
expect(pageItem, findsNothing);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ 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 'tabs_test.dart' as tabs_test;
|
||||
import 'workspace_icon_test.dart' as workspace_icon_test;
|
||||
import 'workspace_settings_test.dart' as workspace_settings_test;
|
||||
|
||||
|
@ -14,4 +15,5 @@ void main() {
|
|||
collaborative_workspace_test.main();
|
||||
change_name_and_icon_test.main();
|
||||
workspace_icon_test.main();
|
||||
tabs_test.main();
|
||||
}
|
||||
|
|
|
@ -299,6 +299,8 @@ void main() {
|
|||
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.dismissCellEditor();
|
||||
|
||||
await tester.tapDatabaseFilterButton();
|
||||
|
@ -308,19 +310,32 @@ void main() {
|
|||
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(2);
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
await tester.openCalendarEvent(index: 0, date: secondOfThisMonth);
|
||||
await tester.tapButton(finderForFieldType(FieldType.MultiSelect));
|
||||
await tester.selectOption(name: "asdf");
|
||||
await tester.dismissCellEditor();
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
tester.assertNumberOfEventsInCalendar(0);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.changeSelectFilterCondition(
|
||||
|
|
|
@ -211,7 +211,7 @@ void main() {
|
|||
await tester.toggleIncludeTime();
|
||||
|
||||
// Select a date
|
||||
final now = DateTime.now();
|
||||
DateTime now = DateTime.now();
|
||||
await tester.selectDay(content: now.day);
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
@ -225,7 +225,7 @@ void main() {
|
|||
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
|
||||
// Toggle include time
|
||||
// When toggling include time, the time value is from the previous existing date time, not the current time
|
||||
now = DateTime.now();
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
|
|
@ -538,8 +538,8 @@ void main() {
|
|||
|
||||
// edit the first date cell
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.toggleIncludeTime();
|
||||
final now = DateTime.now();
|
||||
await tester.toggleIncludeTime();
|
||||
await tester.selectDay(content: now.day);
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
@ -582,7 +582,6 @@ void main() {
|
|||
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,
|
||||
|
|
|
@ -108,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();
|
||||
});
|
||||
|
|
|
@ -21,7 +21,6 @@ import 'package:path_provider/path_provider.dart';
|
|||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
@ -69,10 +68,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect one file
|
||||
expect(find.byType(RenderMedia), findsOneWidget);
|
||||
|
@ -85,9 +81,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.tapFileUploadHint();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect two files
|
||||
|
@ -139,10 +133,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect two files
|
||||
expect(find.byType(RenderMedia), findsNWidgets(2));
|
||||
|
@ -193,10 +184,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect two files
|
||||
expect(find.byType(RenderMedia), findsNWidgets(2));
|
||||
|
@ -230,7 +218,7 @@ void main() {
|
|||
await Future.wait([firstFile.delete(), secondFile.delete()]);
|
||||
});
|
||||
|
||||
testWidgets('hide file names', (tester) async {
|
||||
testWidgets('show file names', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
|
@ -272,10 +260,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect two files
|
||||
expect(find.byType(RenderMedia), findsNWidgets(2));
|
||||
|
@ -283,28 +268,28 @@ void main() {
|
|||
await tester.dismissCellEditor();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Open first row in row detail view then toggle hide file names
|
||||
// Open first row in row detail view then toggle show file names
|
||||
await tester.openFirstRowDetailPage();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect file names to not be shown (hidden)
|
||||
expect(find.text('sample.jpeg'), findsNothing);
|
||||
expect(find.text('sample.gif'), findsNothing);
|
||||
|
||||
await tester.tapGridFieldWithNameInRowDetailPage('Type');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Toggle show file names
|
||||
await tester.tap(find.byType(Toggle));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect file names to be shown
|
||||
expect(find.text('sample.jpeg'), findsOneWidget);
|
||||
expect(find.text('sample.gif'), findsOneWidget);
|
||||
|
||||
await tester.tapGridFieldWithNameInRowDetailPage('Type');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Toggle hide file names
|
||||
await tester.tap(find.byType(Toggle));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.dismissRowDetailPage();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect file names to be hidden
|
||||
expect(find.text('sample.jpeg'), findsNothing);
|
||||
expect(find.text('sample.gif'), findsNothing);
|
||||
|
||||
// Remove the temp files
|
||||
await Future.wait([firstFile.delete(), secondFile.delete()]);
|
||||
});
|
||||
|
|
|
@ -4,12 +4,10 @@ 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/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';
|
||||
|
@ -26,63 +24,6 @@ void main() {
|
|||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('database row cover', () {
|
||||
testWidgets('add image to media field and check if cover is set (grid)',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// Change to media type
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.Media);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// Prepare file for upload from local
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final tempDirectory = await getTemporaryDirectory();
|
||||
|
||||
final imagePath = p.join(tempDirectory.path, 'sample.jpeg');
|
||||
final file = File(imagePath)
|
||||
..writeAsBytesSync(image.buffer.asUint8List());
|
||||
|
||||
mockPickFilePaths(paths: [imagePath]);
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
|
||||
// Open media cell editor
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.Media);
|
||||
await tester.findMediaCellEditor(findsOneWidget);
|
||||
|
||||
// Click on add file button in the Media Cell Editor
|
||||
await tester.tap(find.text(LocaleKeys.grid_media_addFileOrImage.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
|
||||
// Expect one file
|
||||
expect(find.byType(RenderMedia), findsOneWidget);
|
||||
|
||||
// Close cell editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Open first row in row detail view
|
||||
await tester.openFirstRowDetailPage();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect a cover to be shown
|
||||
expect(find.byType(RowCover), findsOneWidget);
|
||||
|
||||
// Remove the temp file
|
||||
await Future.wait([file.delete()]);
|
||||
});
|
||||
|
||||
testWidgets('add and remove cover from Row Detail Card', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
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';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart';
|
||||
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:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy/plugins/document/document_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.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-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.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';
|
||||
|
@ -76,6 +76,24 @@ void main() {
|
|||
// The number of emoji should be two. One in the row displayed in the grid
|
||||
// one in the row detail page.
|
||||
expect(emojiText, findsNWidgets(2));
|
||||
|
||||
// insert a sub page in database
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_subPage_name.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// the row detail page should be closed
|
||||
final rowDetailPage = find.byType(RowDetailPage);
|
||||
await tester.pumpUntilNotFound(rowDetailPage);
|
||||
|
||||
// expect to see a document page
|
||||
final documentPage = find.byType(DocumentPage);
|
||||
expect(documentPage, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('remove emoji', (tester) async {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'database_cell_test.dart' as database_cell_test;
|
||||
import 'database_field_settings_test.dart' as database_field_settings_test;
|
||||
import 'database_field_test.dart' as database_field_test;
|
||||
import 'database_row_page_test.dart' as database_row_page_test;
|
||||
import 'database_setting_test.dart' as database_setting_test;
|
||||
import 'database_share_test.dart' as database_share_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
database_cell_test.main();
|
||||
database_field_test.main();
|
||||
database_field_settings_test.main();
|
||||
database_share_test.main();
|
||||
database_row_page_test.main();
|
||||
database_setting_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'database_calendar_test.dart' as database_calendar_test;
|
||||
import 'database_filter_test.dart' as database_filter_test;
|
||||
import 'database_media_test.dart' as database_media_test;
|
||||
import 'database_row_cover_test.dart' as database_row_cover_test;
|
||||
import 'database_share_test.dart' as database_share_test;
|
||||
import 'database_sort_test.dart' as database_sort_test;
|
||||
import 'database_view_test.dart' as database_view_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
database_filter_test.main();
|
||||
database_sort_test.main();
|
||||
database_view_test.main();
|
||||
database_calendar_test.main();
|
||||
database_media_test.main();
|
||||
database_row_cover_test.main();
|
||||
database_share_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
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() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Block option interaction tests', () {
|
||||
testWidgets('has correct block selection on tap option button',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// We edit the document by entering some characters, to ensure the document has focus
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [2])),
|
||||
);
|
||||
|
||||
// Insert character 'a' three times - easy to identify
|
||||
await tester.ime.insertText('aaa');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([2]);
|
||||
expect(node?.delta?.toPlainText(), startsWith('aaa'));
|
||||
|
||||
final multiSelection = Selection(
|
||||
start: Position(path: [2], offset: 3),
|
||||
end: Position(path: [4], offset: 40),
|
||||
);
|
||||
|
||||
// Select multiple items
|
||||
await tester.editor.updateSelection(multiSelection);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Press the block option menu
|
||||
await tester.editor.hoverAndClickOptionMenuButton([2]);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect the selection to be Block type and not have changed
|
||||
expect(editorState.selectionType, SelectionType.block);
|
||||
expect(editorState.selection, multiSelection);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -42,8 +42,10 @@ void main() {
|
|||
expect(mentionBlock, findsOneWidget);
|
||||
|
||||
// Delete the page
|
||||
await tester.hoverOnPageName(name);
|
||||
await tester.tapDeletePageButton();
|
||||
await tester.hoverOnPageName(
|
||||
name,
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Navigate to the deleted page from the inline mention
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/startup/startup.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/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
String generateRandomString(int len) {
|
||||
final r = Random();
|
||||
return String.fromCharCodes(
|
||||
List.generate(len, (index) => r.nextInt(33) + 89),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'document find menu test',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
// tap editor to get focus
|
||||
await tester.tapButton(find.byType(AppFlowyEditor));
|
||||
|
||||
// set clipboard data
|
||||
final data = [
|
||||
"123456\n",
|
||||
...List.generate(100, (_) => "${generateRandomString(50)}\n"),
|
||||
"1234567\n",
|
||||
...List.generate(100, (_) => "${generateRandomString(50)}\n"),
|
||||
"12345678\n",
|
||||
...List.generate(100, (_) => "${generateRandomString(50)}\n"),
|
||||
].join();
|
||||
await getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(
|
||||
plainText: data,
|
||||
),
|
||||
);
|
||||
|
||||
// paste
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed:
|
||||
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// go back to beginning of document
|
||||
// FIXME: Cannot run Ctrl+F unless selection is on screen
|
||||
await tester.editor
|
||||
.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FindAndReplaceMenuWidget), findsNothing);
|
||||
|
||||
// press cmd/ctrl+F to display the find menu
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyF,
|
||||
isControlPressed:
|
||||
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FindAndReplaceMenuWidget), findsOneWidget);
|
||||
|
||||
final textField = find.descendant(
|
||||
of: find.byType(FindAndReplaceMenuWidget),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
|
||||
await tester.enterText(
|
||||
textField,
|
||||
"123456",
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.text("123456", findRichText: true),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.text("1234567", findRichText: true),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.showKeyboard(textField);
|
||||
await tester.idle();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.text("12345678", findRichText: true),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// tap next button, go back to beginning of document
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(FindMenu),
|
||||
matching: find.byFlowySvg(FlowySvgs.arrow_down_s),
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.text("123456", findRichText: true),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
/// press cmd/ctrl+F to display the find menu
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyF,
|
||||
isControlPressed:
|
||||
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FindAndReplaceMenuWidget), findsOneWidget);
|
||||
|
||||
/// press esc to dismiss the find menu
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(FindAndReplaceMenuWidget), findsNothing);
|
||||
},
|
||||
);
|
||||
}
|
|
@ -0,0 +1,382 @@
|
|||
import 'dart:io';
|
||||
|
||||
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/inline_actions_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.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/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/keyboard.dart';
|
||||
import '../../shared/util.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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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));
|
||||
});
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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.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);
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
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));
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
testWidgets('Cancel inline page reference menu by space', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showPlusMenu();
|
||||
|
||||
// Cancel by space
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.space,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(InlineActionsMenu), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ 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';
|
||||
|
||||
|
@ -59,7 +60,7 @@ void main() {
|
|||
expect(editorState.getNodeAtPath([2])?.delta?.toPlainText(), isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('turn into', (tester) async {
|
||||
testWidgets('turn into - single line', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
|
@ -97,5 +98,83 @@ void main() {
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
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])));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
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/workspace/presentation/home/menu/view/view_action_type.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';
|
||||
|
||||
|
@ -73,10 +73,7 @@ void main() {
|
|||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
@ -99,10 +96,7 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionAddButton([0], false);
|
||||
|
@ -155,10 +149,7 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionAddButton([0], false);
|
||||
|
@ -216,10 +207,7 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor
|
||||
|
@ -260,10 +248,7 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor
|
||||
|
@ -313,10 +298,7 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
@ -326,6 +308,11 @@ void main() {
|
|||
expect(find.text('Child page'), findsNothing);
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
|
||||
// Since there is no selection active in editor before deleting Node,
|
||||
// we need to give focus back to the editor
|
||||
await tester.editor
|
||||
.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
|
@ -354,10 +341,7 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
// Delete
|
||||
|
@ -405,15 +389,16 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
|
||||
await tester.hoverOnPageName('Child page');
|
||||
await tester.tapDeletePageButton();
|
||||
await tester.hoverOnPageName(
|
||||
'Child page',
|
||||
onHover: () async {
|
||||
await tester.tapDeletePageButton();
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
|
@ -432,10 +417,7 @@ void main() {
|
|||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(_defaultPageName);
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
@ -473,6 +455,49 @@ void main() {
|
|||
expect(afterNode.type, beforeNode.type);
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('turn into page', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
|
||||
// Insert nested list
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNode(
|
||||
[0],
|
||||
bulletedListNode(
|
||||
text: 'Parent',
|
||||
children: [
|
||||
bulletedListNode(text: 'Child 1'),
|
||||
bulletedListNode(text: 'Child 2'),
|
||||
],
|
||||
),
|
||||
);
|
||||
await editorState.apply(transaction);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_optionAction_turnInto.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text(LocaleKeys.editor_page.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
expect(find.text('Parent'), findsNWidgets(2));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -498,4 +523,16 @@ extension _SubPageTestHelper on WidgetTester {
|
|||
|
||||
await pumpUntilFound(find.byType(SubPageBlockComponent));
|
||||
}
|
||||
|
||||
Future<void> renamePageWithSecondary(
|
||||
String currentName,
|
||||
String newName,
|
||||
) async {
|
||||
await hoverOnPageName(currentName, onHover: () async => pumpAndSettle());
|
||||
await rightClickOnPageName(currentName);
|
||||
await tapButtonWithName(ViewMoreActionType.rename.name);
|
||||
await enterText(find.byType(TextFormField), newName);
|
||||
await tapOKButton();
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_alignment_test.dart' as document_alignment_test;
|
||||
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_inline_page_reference_test.dart'
|
||||
as document_inline_page_reference_test;
|
||||
import 'document_more_actions_test.dart' as document_more_actions_test;
|
||||
import 'document_option_action_test.dart' as document_option_action_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;
|
||||
import 'document_with_file_test.dart' as document_with_file_test;
|
||||
import 'document_with_image_block_test.dart' as document_with_image_block_test;
|
||||
import 'document_with_inline_math_equation_test.dart'
|
||||
as document_with_inline_math_equation_test;
|
||||
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
||||
import 'document_with_multi_image_block_test.dart'
|
||||
as document_with_multi_image_block_test;
|
||||
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 main() {
|
||||
|
@ -34,18 +19,5 @@ void main() {
|
|||
document_with_inline_page_test.main();
|
||||
document_with_inline_math_equation_test.main();
|
||||
document_with_cover_image_test.main();
|
||||
document_with_outline_block.main();
|
||||
document_with_toggle_list_test.main();
|
||||
document_copy_and_paste_test.main();
|
||||
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
|
||||
// Don't add new tests here.
|
||||
}
|
||||
|
|
|
@ -2,20 +2,25 @@ 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_inline_sub_page_test.dart' as document_inline_sub_page_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_with_toggle_heading_block_test.dart'
|
||||
as document_with_toggle_heading_block_test;
|
||||
import 'document_sub_page_test.dart' as document_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();
|
||||
document_with_toggle_heading_block_test.main();
|
||||
document_sub_page_test.main();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_alignment_test.dart' as document_alignment_test;
|
||||
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_text_direction_test.dart' as document_text_direction_test;
|
||||
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;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
document_with_outline_block.main();
|
||||
document_with_toggle_list_test.main();
|
||||
document_copy_and_paste_test.main();
|
||||
document_codeblock_paste_test.main();
|
||||
document_alignment_test.main();
|
||||
document_text_direction_test.main();
|
||||
|
||||
// Don't add new tests here.
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_block_option_test.dart' as document_block_option_test;
|
||||
import 'document_find_menu_test.dart' as document_find_menu_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_toolbar_test.dart' as document_toolbar_test;
|
||||
import 'document_with_file_test.dart' as document_with_file_test;
|
||||
import 'document_with_image_block_test.dart' as document_with_image_block_test;
|
||||
import 'document_with_multi_image_block_test.dart'
|
||||
as document_with_multi_image_block_test;
|
||||
import 'document_with_simple_table_test.dart'
|
||||
as document_with_simple_table_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
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();
|
||||
document_block_option_test.main();
|
||||
document_find_menu_test.main();
|
||||
document_toolbar_test.main();
|
||||
document_with_simple_table_test.main();
|
||||
}
|
|
@ -347,5 +347,27 @@ void main() {
|
|||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('paste text in title, check if the text is updated',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
await Clipboard.setData(const ClipboardData(text: _testDocumentName));
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.tapButton(title);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final newTitle = tester.editor.findDocumentTitle(_testDocumentName);
|
||||
expect(newTitle, findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
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() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document toolbar:', () {
|
||||
testWidgets('font family', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
const text = 'font family';
|
||||
await tester.ime.insertText(text);
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
|
||||
// tap the font family button
|
||||
final fontFamilyButton = find.byKey(kFontFamilyToolbarItemKey);
|
||||
await tester.tapButton(fontFamilyButton);
|
||||
|
||||
// expect to see the font family dropdown immediately
|
||||
expect(find.byType(FontFamilyDropDown), findsOneWidget);
|
||||
|
||||
// click the font family 'Abel'
|
||||
const abel = 'Abel';
|
||||
await tester.tapButton(find.text(abel));
|
||||
|
||||
// check the text is updated to 'Abel'
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(
|
||||
editorState.getDeltaAttributeValueInSelection(
|
||||
AppFlowyRichTextKeys.fontFamily,
|
||||
),
|
||||
abel,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -147,7 +148,7 @@ void main() {
|
|||
tester.expectViewHasIcon(
|
||||
gettingStarted,
|
||||
ViewLayoutPB.Document,
|
||||
punch,
|
||||
EmojiIconData.emoji(punch),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
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';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/footer/grid_footer.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/row.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.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';
|
||||
|
||||
|
@ -174,9 +177,110 @@ void main() {
|
|||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert a referenced grid with many rows (load more option)',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await insertLinkedDatabase(tester, ViewLayoutPB.Grid);
|
||||
|
||||
// validate the referenced grid is inserted
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.byType(GridPage),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// https://github.com/AppFlowy-IO/AppFlowy/issues/3533
|
||||
// test: the selection of editor should be clear when editing the grid
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(
|
||||
Position(path: [1]),
|
||||
),
|
||||
);
|
||||
final gridTextCell = find.byType(EditableTextCell).first;
|
||||
await tester.tapButton(gridTextCell);
|
||||
|
||||
expect(tester.editor.getCurrentEditorState().selection, isNull);
|
||||
|
||||
final editorScrollable = find
|
||||
.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.byWidgetPredicate(
|
||||
(w) => w is Scrollable && w.axis == Axis.vertical,
|
||||
),
|
||||
)
|
||||
.first;
|
||||
|
||||
// Add 100 Rows to the linked database
|
||||
final addRowFinder = find.byType(GridAddRowButton);
|
||||
for (var i = 0; i < 100; i++) {
|
||||
await tester.scrollUntilVisible(
|
||||
addRowFinder,
|
||||
100,
|
||||
scrollable: editorScrollable,
|
||||
);
|
||||
await tester.tapButton(addRowFinder);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
// Since all rows visible are those we added, we should see all of them
|
||||
expect(find.byType(GridRow), findsNWidgets(103));
|
||||
|
||||
// Navigate to getting started
|
||||
await tester.openPage(gettingStarted);
|
||||
|
||||
// Navigate back to the document
|
||||
await tester.openPage('insert_a_reference_${ViewLayoutPB.Grid.name}');
|
||||
|
||||
// We see only 25 Grid Rows
|
||||
expect(find.byType(GridRow), findsNWidgets(25));
|
||||
|
||||
// We see Add row and load more button
|
||||
expect(find.byType(GridAddRowButton), findsOneWidget);
|
||||
expect(find.byType(GridRowLoadMoreButton), findsOneWidget);
|
||||
|
||||
// Load more rows, expect 50 visible
|
||||
await _loadMoreRows(tester, editorScrollable, 50);
|
||||
|
||||
// Load more rows, expect 75 visible
|
||||
await _loadMoreRows(tester, editorScrollable, 75);
|
||||
|
||||
// Load more rows, expect 100 visible
|
||||
await _loadMoreRows(tester, editorScrollable, 100);
|
||||
|
||||
// Load more rows, expect 103 visible
|
||||
await _loadMoreRows(tester, editorScrollable, 103);
|
||||
|
||||
// We no longer see load more option
|
||||
expect(find.byType(GridRowLoadMoreButton), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadMoreRows(
|
||||
WidgetTester tester,
|
||||
Finder scrollable, [
|
||||
int? expectedRows,
|
||||
]) async {
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(GridRowLoadMoreButton),
|
||||
100,
|
||||
scrollable: scrollable,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byType(GridRowLoadMoreButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
if (expectedRows != null) {
|
||||
expect(find.byType(GridRow), findsNWidgets(expectedRows));
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a referenced database of [layout] into the document
|
||||
Future<void> insertLinkedDatabase(
|
||||
WidgetTester tester,
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
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_date_block.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/desktop_date_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.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 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
|
@ -18,7 +25,7 @@ void main() {
|
|||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('date or reminder block in document', () {
|
||||
group('date or reminder block in document:', () {
|
||||
testWidgets("insert date with time block", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
@ -33,7 +40,6 @@ void main() {
|
|||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
|
@ -57,13 +63,15 @@ void main() {
|
|||
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,
|
||||
);
|
||||
final textField = find
|
||||
.descendant(
|
||||
of: find.byType(DesktopAppFlowyDatePicker),
|
||||
matching: find.byType(TextField),
|
||||
)
|
||||
.last;
|
||||
await tester.pumpUntilFound(textField);
|
||||
await tester.enterText(textField, "11:12");
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// we will get field with current date and 11:12 as time
|
||||
|
@ -85,7 +93,6 @@ void main() {
|
|||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
|
@ -121,5 +128,339 @@ void main() {
|
|||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("copy, cut and paste a date mention", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'copy, cut and paste a date mention',
|
||||
);
|
||||
|
||||
// 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(),
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
// update selection and copy
|
||||
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();
|
||||
|
||||
// update selection and paste
|
||||
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.byType(MentionDateBlock), findsNWidgets(2));
|
||||
expect(find.text('@$formattedDate'), findsNWidgets(2));
|
||||
|
||||
// update selection and cut
|
||||
await tester.editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [0], offset: 1),
|
||||
end: Position(path: [0], offset: 2),
|
||||
),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
|
||||
// update selection and paste
|
||||
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.byType(MentionDateBlock), findsNWidgets(2));
|
||||
expect(find.text('@$formattedDate'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets("copy, cut and paste a reminder mention", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'copy, cut and paste a reminder mention',
|
||||
);
|
||||
|
||||
// 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(),
|
||||
);
|
||||
|
||||
// trigger popup
|
||||
await tester.tapButton(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// set date to be fifteenth of the next month
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(DesktopAppFlowyDatePicker),
|
||||
matching: find.byFlowySvg(FlowySvgs.arrow_right_s),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(TableCalendar),
|
||||
matching: find.text(15.toString()),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// add a reminder
|
||||
await tester.tap(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(LocaleKeys.datePicker_reminderLabel.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.textContaining(
|
||||
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// verify
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
dateFormat: UserDateFormatPB.Friendly,
|
||||
timeFormat: UserTimeFormatPB.TwentyFourHour,
|
||||
);
|
||||
final now = DateTime.now();
|
||||
final fifteenthOfNextMonth = DateTime(now.year, now.month + 1, 15);
|
||||
final formattedDate =
|
||||
dateTimeSettings.dateFormat.formatDate(fifteenthOfNextMonth, false);
|
||||
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
|
||||
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
|
||||
|
||||
// update selection and copy
|
||||
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();
|
||||
|
||||
// update selection and paste
|
||||
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.byType(MentionDateBlock), findsNWidgets(2));
|
||||
expect(find.text('@$formattedDate'), findsNWidgets(2));
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNWidgets(2));
|
||||
expect(
|
||||
getIt<ReminderBloc>().state.reminders.map((e) => e.id).toSet().length,
|
||||
2,
|
||||
);
|
||||
|
||||
// update selection and cut
|
||||
await tester.editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [0], offset: 1),
|
||||
end: Position(path: [0], offset: 2),
|
||||
),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
|
||||
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
|
||||
|
||||
// update selection and paste
|
||||
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.byType(MentionDateBlock), findsNWidgets(2));
|
||||
expect(find.text('@$formattedDate'), findsNWidgets(2));
|
||||
expect(find.byType(MentionDateBlock), findsNWidgets(2));
|
||||
expect(find.text('@$formattedDate'), findsNWidgets(2));
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNWidgets(2));
|
||||
expect(
|
||||
getIt<ReminderBloc>().state.reminders.map((e) => e.id).toSet().length,
|
||||
2,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets("delete, undo and redo a reminder mention", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'delete, undo and redo a reminder mention',
|
||||
);
|
||||
|
||||
// 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(),
|
||||
);
|
||||
|
||||
// trigger popup
|
||||
await tester.tapButton(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// set date to be fifteenth of the next month
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(DesktopAppFlowyDatePicker),
|
||||
matching: find.byFlowySvg(FlowySvgs.arrow_right_s),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(TableCalendar),
|
||||
matching: find.text(15.toString()),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// add a reminder
|
||||
await tester.tap(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(LocaleKeys.datePicker_reminderLabel.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.textContaining(
|
||||
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// verify
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
dateFormat: UserDateFormatPB.Friendly,
|
||||
timeFormat: UserTimeFormatPB.TwentyFourHour,
|
||||
);
|
||||
final now = DateTime.now();
|
||||
final fifteenthOfNextMonth = DateTime(now.year, now.month + 1, 15);
|
||||
final formattedDate =
|
||||
dateTimeSettings.dateFormat.formatDate(fifteenthOfNextMonth, false);
|
||||
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
|
||||
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
|
||||
|
||||
// update selection and backspace to delete the mention
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(MentionDateBlock), findsNothing);
|
||||
expect(find.text('@$formattedDate'), findsNothing);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNothing);
|
||||
expect(getIt<ReminderBloc>().state.reminders.isEmpty, isTrue);
|
||||
|
||||
// undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
|
||||
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
|
||||
|
||||
// redo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
|
||||
expect(find.byType(MentionDateBlock), findsNothing);
|
||||
expect(find.text('@$formattedDate'), findsNothing);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNothing);
|
||||
expect(getIt<ReminderBloc>().state.reminders.isEmpty, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -47,9 +47,7 @@ void main() {
|
|||
mockPickFilePaths(paths: [filePath]);
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
await tester.tap(
|
||||
find.text(LocaleKeys.document_plugins_file_fileUploadHint.tr()),
|
||||
);
|
||||
await tester.tapFileUploadHint();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FileUploadMenu), findsNothing);
|
||||
|
|
|
@ -29,6 +29,58 @@ void main() {
|
|||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('image block in document', () {
|
||||
Future<void> testEmbedImage(WidgetTester tester, String url) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_image.tr(),
|
||||
);
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ImagePlaceholder),
|
||||
matching: find.byType(AppFlowyPopover),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
);
|
||||
await tester.enterText(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.byType(TextField),
|
||||
),
|
||||
url,
|
||||
);
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.text(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ResizableImage), findsOneWidget);
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(node.type, ImageBlockKeys.type);
|
||||
expect(node.attributes[ImageBlockKeys.url], url);
|
||||
}
|
||||
|
||||
testWidgets('insert an image from local file', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
@ -79,58 +131,18 @@ void main() {
|
|||
file.deleteSync();
|
||||
});
|
||||
|
||||
testWidgets('insert an image from network', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
testWidgets('insert a gif image from network', (tester) async {
|
||||
await testEmbedImage(
|
||||
tester,
|
||||
'https://www.easygifanimator.net/images/samples/sparkles.gif',
|
||||
);
|
||||
});
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
|
||||
testWidgets('insert a jpg image from network', (tester) async {
|
||||
await testEmbedImage(
|
||||
tester,
|
||||
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_image.tr(),
|
||||
);
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ImagePlaceholder),
|
||||
matching: find.byType(AppFlowyPopover),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
);
|
||||
const url =
|
||||
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640';
|
||||
await tester.enterText(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.byType(TextField),
|
||||
),
|
||||
url,
|
||||
);
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.text(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ResizableImage), findsOneWidget);
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(node.type, ImageBlockKeys.type);
|
||||
expect(node.attributes[ImageBlockKeys.url], url);
|
||||
});
|
||||
|
||||
testWidgets('insert an image from unsplash', (tester) async {
|
||||
|
|
|
@ -113,5 +113,55 @@ void main() {
|
|||
|
||||
tester.expectToSeeText(formula);
|
||||
});
|
||||
|
||||
testWidgets('insert a inline math equation and type something after it',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'math equation',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// insert a inline page
|
||||
const formula = 'E = MC ^ 2';
|
||||
await tester.ime.insertText(formula);
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
final inlineMathEquationButton = find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
|
||||
// expect to see the math equation block
|
||||
final inlineMathEquation = find.byType(InlineMathEquation);
|
||||
expect(inlineMathEquation, findsOneWidget);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
const text = 'Hello World';
|
||||
await tester.ime.insertText(text);
|
||||
|
||||
final inlineText = find.textContaining(text, findRichText: true);
|
||||
expect(inlineText, findsOneWidget);
|
||||
|
||||
// the text should be in the same line with the math equation
|
||||
final inlineMathEquationPosition = tester.getRect(inlineMathEquation);
|
||||
final textPosition = tester.getRect(inlineText);
|
||||
// allow 5px difference
|
||||
expect(
|
||||
(textPosition.top - inlineMathEquationPosition.top).abs(),
|
||||
lessThan(5),
|
||||
);
|
||||
expect(
|
||||
(textPosition.bottom - inlineMathEquationPosition.bottom).abs(),
|
||||
lessThan(5),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -94,6 +94,20 @@ void main() {
|
|||
await tester.tapButton(finder);
|
||||
expect(find.byType(GridPage), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('insert a inline page and type something after the page',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await insertInlinePage(tester, ViewLayoutPB.Grid);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
const text = 'Hello World';
|
||||
await tester.ime.insertText(text);
|
||||
|
||||
expect(find.textContaining(text, findRichText: true), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.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';
|
||||
|
@ -26,7 +25,6 @@ import 'package:path_provider/path_provider.dart';
|
|||
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
|
@ -50,7 +48,7 @@ void main() {
|
|||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
|
||||
offset: 100,
|
||||
offset: 80,
|
||||
);
|
||||
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
|
||||
|
@ -148,7 +146,7 @@ void main() {
|
|||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
|
||||
offset: 100,
|
||||
offset: 80,
|
||||
);
|
||||
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.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';
|
||||
|
||||
|
@ -43,6 +44,9 @@ void main() {
|
|||
* # Heading 1
|
||||
* ## Heading 2
|
||||
* ### Heading 3
|
||||
* > # Heading 1
|
||||
* > ## Heading 2
|
||||
* > ### Heading 3
|
||||
*/
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(3);
|
||||
|
@ -53,7 +57,7 @@ void main() {
|
|||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading1),
|
||||
),
|
||||
findsOneWidget,
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// Heading 2 is prefixed with a bullet
|
||||
|
@ -62,7 +66,7 @@ void main() {
|
|||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading2),
|
||||
),
|
||||
findsOneWidget,
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// Heading 3 is prefixed with a dash
|
||||
|
@ -71,7 +75,7 @@ void main() {
|
|||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading3),
|
||||
),
|
||||
findsOneWidget,
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// update the Heading 1 to Heading 1Hello world
|
||||
|
@ -99,13 +103,16 @@ void main() {
|
|||
* # Heading 1
|
||||
* ## Heading 2
|
||||
* ### Heading 3
|
||||
* > # Heading 1
|
||||
* > ## Heading 2
|
||||
* > ### Heading 3
|
||||
*/
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(3);
|
||||
await tester.editor.tapLineOfEditorAt(7);
|
||||
await insertOutlineInDocument(tester);
|
||||
|
||||
// expect to find only the `heading1` widget under the [OutlineBlockWidget]
|
||||
await hoverAndClickDepthOptionAction(tester, [3], 1);
|
||||
await hoverAndClickDepthOptionAction(tester, [6], 1);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
|
@ -123,7 +130,7 @@ void main() {
|
|||
//////
|
||||
|
||||
/// expect to find only the 'heading1' and 'heading2' under the [OutlineBlockWidget]
|
||||
await hoverAndClickDepthOptionAction(tester, [3], 2);
|
||||
await hoverAndClickDepthOptionAction(tester, [6], 2);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
|
@ -134,13 +141,13 @@ void main() {
|
|||
//////
|
||||
|
||||
// expect to find all the headings under the [OutlineBlockWidget]
|
||||
await hoverAndClickDepthOptionAction(tester, [3], 3);
|
||||
await hoverAndClickDepthOptionAction(tester, [6], 3);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading1),
|
||||
),
|
||||
findsOneWidget,
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -148,7 +155,7 @@ void main() {
|
|||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading2),
|
||||
),
|
||||
findsOneWidget,
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -156,7 +163,7 @@ void main() {
|
|||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading3),
|
||||
),
|
||||
findsOneWidget,
|
||||
findsNWidgets(2),
|
||||
);
|
||||
//////
|
||||
});
|
||||
|
@ -169,7 +176,6 @@ Future<void> insertOutlineInDocument(WidgetTester tester) async {
|
|||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_outline.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
@ -187,7 +193,17 @@ Future<void> hoverAndClickDepthOptionAction(
|
|||
|
||||
Future<void> insertHeadingComponent(WidgetTester tester) async {
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
// # heading 1-3
|
||||
await tester.ime.insertText('# $heading1\n');
|
||||
await tester.ime.insertText('## $heading2\n');
|
||||
await tester.ime.insertText('### $heading3\n');
|
||||
|
||||
// > # toggle heading 1-3
|
||||
await tester.ime.insertText('> # $heading1\n');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
await tester.ime.insertText('> ## $heading2\n');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
await tester.ime.insertText('> ### $heading3\n');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,686 @@
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.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';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
const String heading1 = "Heading 1";
|
||||
const String heading2 = "Heading 2";
|
||||
const String heading3 = "Heading 3";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('simple table block test:', () {
|
||||
testWidgets('insert a simple table block', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// validate the table is inserted
|
||||
expect(find.byType(SimpleTableBlockWidget), findsOneWidget);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(
|
||||
editorState.selection,
|
||||
// table -> row -> cell -> paragraph
|
||||
Selection.collapsed(Position(path: [0, 0, 0, 0])),
|
||||
);
|
||||
|
||||
final firstCell = find.byType(SimpleTableCellBlockWidget).first;
|
||||
expect(
|
||||
tester
|
||||
.state<SimpleTableCellBlockWidgetState>(firstCell)
|
||||
.isEditingCellNotifier
|
||||
.value,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('select all in table cell', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
const cell1Content = 'Cell 1';
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('New Table');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
await tester.insertTableInDocument();
|
||||
await tester.ime.insertText(cell1Content);
|
||||
await tester.pumpAndSettle();
|
||||
// Select all in the cell
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection(
|
||||
start: Position(path: [1, 0, 0, 0]),
|
||||
end: Position(path: [1, 0, 0, 0], offset: cell1Content.length),
|
||||
),
|
||||
);
|
||||
|
||||
// Press select all again, the selection should be the entire document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [1, 1, 1, 0]),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('''
|
||||
1. hover on the table
|
||||
1.1 click the add row button
|
||||
1.2 click the add column button
|
||||
1.3 click the add row and column button
|
||||
2. validate the table is updated
|
||||
3. delete the last column
|
||||
4. delete the last row
|
||||
5. validate the table is updated
|
||||
''', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// add a new row
|
||||
final row = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableRowBlockWidget && w.node.rowIndex == 1;
|
||||
});
|
||||
await tester.hoverOnWidget(
|
||||
row,
|
||||
onHover: () async {
|
||||
final addRowButton = find.byType(SimpleTableAddRowButton).first;
|
||||
await tester.tap(addRowButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// add a new column
|
||||
final column = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableCellBlockWidget && w.node.columnIndex == 1;
|
||||
}).first;
|
||||
await tester.hoverOnWidget(
|
||||
column,
|
||||
onHover: () async {
|
||||
final addColumnButton = find.byType(SimpleTableAddColumnButton).first;
|
||||
await tester.tap(addColumnButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// add a new row and a new column
|
||||
final row2 = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableCellBlockWidget &&
|
||||
w.node.rowIndex == 2 &&
|
||||
w.node.columnIndex == 2;
|
||||
}).first;
|
||||
await tester.hoverOnWidget(
|
||||
row2,
|
||||
onHover: () async {
|
||||
// click the add row and column button
|
||||
final addRowAndColumnButton =
|
||||
find.byType(SimpleTableAddColumnAndRowButton).first;
|
||||
await tester.tap(addRowAndColumnButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.columnLength, 4);
|
||||
expect(tableNode.rowLength, 4);
|
||||
|
||||
// delete the last row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: tableNode.rowLength - 1,
|
||||
action: SimpleTableMoreAction.delete,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tableNode.rowLength, 3);
|
||||
expect(tableNode.columnLength, 4);
|
||||
|
||||
// delete the last column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: tableNode.columnLength - 1,
|
||||
action: SimpleTableMoreAction.delete,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tableNode.columnLength, 3);
|
||||
expect(tableNode.rowLength, 3);
|
||||
});
|
||||
|
||||
testWidgets('enable header column and header row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// enable the header row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderRow,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
// enable the header column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderColumn,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
|
||||
expect(tableNode.isHeaderColumnEnabled, isTrue);
|
||||
expect(tableNode.isHeaderRowEnabled, isTrue);
|
||||
|
||||
// disable the header row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderRow,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tableNode.isHeaderColumnEnabled, isTrue);
|
||||
expect(tableNode.isHeaderRowEnabled, isFalse);
|
||||
|
||||
// disable the header column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderColumn,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tableNode.isHeaderColumnEnabled, isFalse);
|
||||
expect(tableNode.isHeaderRowEnabled, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('duplicate a column / row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// duplicate the row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.duplicate,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// duplicate the column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.duplicate,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.columnLength, 3);
|
||||
expect(tableNode.rowLength, 3);
|
||||
});
|
||||
|
||||
testWidgets('insert left / insert right', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// insert left
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertLeft,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// insert right
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertRight,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.columnLength, 4);
|
||||
expect(tableNode.rowLength, 2);
|
||||
});
|
||||
|
||||
testWidgets('insert above / insert below', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// insert above
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertAbove,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// insert below
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertBelow,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.rowLength, 4);
|
||||
expect(tableNode.columnLength, 2);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('set column width to page width (1)', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
final tableNode = tester.editor.getNodeAtPath([0]);
|
||||
final beforeWidth = tableNode.width;
|
||||
|
||||
// set the column width to page width
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.setToPageWidth,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth = tableNode.width;
|
||||
expect(afterWidth, greaterThan(beforeWidth));
|
||||
});
|
||||
|
||||
testWidgets('set column width to page width (2)', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
final tableNode = tester.editor.getNodeAtPath([0]);
|
||||
final beforeWidth = tableNode.width;
|
||||
|
||||
// set the column width to page width
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.setToPageWidth,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth = tableNode.width;
|
||||
expect(afterWidth, greaterThan(beforeWidth));
|
||||
});
|
||||
|
||||
testWidgets('distribute columns evenly (1)', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
final tableNode = tester.editor.getNodeAtPath([0]);
|
||||
final beforeWidth = tableNode.width;
|
||||
|
||||
// set the column width to page width
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.distributeColumnsEvenly,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth = tableNode.width;
|
||||
expect(afterWidth, equals(beforeWidth));
|
||||
|
||||
final distributeColumnWidthsEvenly =
|
||||
tableNode.attributes[SimpleTableBlockKeys.distributeColumnWidthsEvenly];
|
||||
expect(distributeColumnWidthsEvenly, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('distribute columns evenly (2)', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
final tableNode = tester.editor.getNodeAtPath([0]);
|
||||
final beforeWidth = tableNode.width;
|
||||
|
||||
// set the column width to page width
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.distributeColumnsEvenly,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth = tableNode.width;
|
||||
expect(afterWidth, equals(beforeWidth));
|
||||
|
||||
final distributeColumnWidthsEvenly =
|
||||
tableNode.attributes[SimpleTableBlockKeys.distributeColumnWidthsEvenly];
|
||||
expect(distributeColumnWidthsEvenly, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('using option menu to set column width', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final beforeWidth = editorState.document.nodeAtPath([0])!.width;
|
||||
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_setToPageWidth.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth = editorState.document.nodeAtPath([0])!.width;
|
||||
expect(afterWidth, greaterThan(beforeWidth));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys
|
||||
.document_plugins_simpleTable_moreActions_distributeColumnsWidth
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth2 = editorState.document.nodeAtPath([0])!.width;
|
||||
expect(afterWidth2, equals(afterWidth));
|
||||
});
|
||||
|
||||
testWidgets('insert a table and use select all the delete it',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
await tester.ime.insertText('Hello World');
|
||||
|
||||
// select all
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
);
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
// only one paragraph left
|
||||
expect(editorState.document.root.children.length, 1);
|
||||
final paragraphNode = editorState.document.nodeAtPath([0])!;
|
||||
expect(paragraphNode.delta, isNull);
|
||||
});
|
||||
|
||||
testWidgets('use tab or shift+tab to navigate in table', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final selection = editorState.selection;
|
||||
expect(selection, isNotNull);
|
||||
expect(selection!.start.path, [0, 0, 1, 0]);
|
||||
expect(selection.end.path, [0, 0, 1, 0]);
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.tab,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final selection2 = editorState.selection;
|
||||
expect(selection2, isNotNull);
|
||||
expect(selection2!.start.path, [0, 0, 0, 0]);
|
||||
expect(selection2.end.path, [0, 0, 0, 0]);
|
||||
});
|
||||
|
||||
testWidgets('shift+enter to insert a new line in table', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.enter,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.document.nodeAtPath([0, 0, 0])!;
|
||||
expect(node.children.length, 1);
|
||||
});
|
||||
|
||||
testWidgets('using option menu to set table align', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final beforeAlign = editorState.document.nodeAtPath([0])!.tableAlign;
|
||||
expect(beforeAlign, TableAlign.left);
|
||||
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_align.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_optionAction_center.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterAlign = editorState.document.nodeAtPath([0])!.tableAlign;
|
||||
expect(afterAlign, TableAlign.center);
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_align.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_optionAction_right.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterAlign2 = editorState.document.nodeAtPath([0])!.tableAlign;
|
||||
expect(afterAlign2, TableAlign.right);
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_align.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_optionAction_left.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterAlign3 = editorState.document.nodeAtPath([0])!.tableAlign;
|
||||
expect(afterAlign3, TableAlign.left);
|
||||
});
|
||||
}
|
||||
|
||||
extension on WidgetTester {
|
||||
/// Insert a table in the document
|
||||
Future<void> insertTableInDocument() async {
|
||||
// open the actions menu and insert the outline block
|
||||
await editor.showSlashMenu();
|
||||
await editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_table.tr(),
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> clickMoreActionItemInTableMenu({
|
||||
required SimpleTableMoreActionType type,
|
||||
required int index,
|
||||
required SimpleTableMoreAction action,
|
||||
}) async {
|
||||
if (type == SimpleTableMoreActionType.row) {
|
||||
final row = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableRowBlockWidget && w.node.rowIndex == index;
|
||||
});
|
||||
await hoverOnWidget(
|
||||
row,
|
||||
onHover: () async {
|
||||
final moreActionButton = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableMoreActionMenu &&
|
||||
w.type == SimpleTableMoreActionType.row &&
|
||||
w.index == index;
|
||||
});
|
||||
await tapButton(moreActionButton);
|
||||
await tapButton(find.text(action.name));
|
||||
},
|
||||
);
|
||||
await pumpAndSettle();
|
||||
} else if (type == SimpleTableMoreActionType.column) {
|
||||
final column = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableCellBlockWidget && w.node.columnIndex == index;
|
||||
}).first;
|
||||
await hoverOnWidget(
|
||||
column,
|
||||
onHover: () async {
|
||||
final moreActionButton = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableMoreActionMenu &&
|
||||
w.type == SimpleTableMoreActionType.column &&
|
||||
w.index == index;
|
||||
});
|
||||
await tapButton(moreActionButton);
|
||||
await tapButton(find.text(action.name));
|
||||
},
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
await tapAt(Offset.zero);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.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';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
const String _heading1 = 'Heading 1';
|
||||
const String _heading2 = 'Heading 2';
|
||||
const String _heading3 = 'Heading 3';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('toggle heading block test:', () {
|
||||
testWidgets('insert toggle heading 1 - 3 block', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'toggle heading block test',
|
||||
);
|
||||
|
||||
for (var i = 1; i <= 3; i++) {
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await _insertToggleHeadingBlockInDocument(tester, i);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is ToggleListBlockComponentWidget &&
|
||||
widget.node.attributes[ToggleListBlockKeys.level] == i,
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('insert toggle heading 1 - 3 block by shortcuts',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'toggle heading block test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('# > $_heading1\n');
|
||||
await tester.ime.insertText('## > $_heading2\n');
|
||||
await tester.ime.insertText('### > $_heading3\n');
|
||||
await tester.ime.insertText('> # $_heading1\n');
|
||||
await tester.ime.insertText('> ## $_heading2\n');
|
||||
await tester.ime.insertText('> ### $_heading3\n');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(ToggleListBlockComponentWidget),
|
||||
findsNWidgets(6),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert toggle heading and convert it to heading',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'toggle heading block test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('# > $_heading1\n');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.ime.insertText('item 1');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [0], offset: _heading1.length),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapButton(find.byType(HeadingPopup));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(HeadingButton),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
|
||||
// tap the H1 button
|
||||
await tester.tapButton(find.byType(HeadingButton).at(0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node1 = editorState.document.nodeAtPath([0])!;
|
||||
expect(node1.type, HeadingBlockKeys.type);
|
||||
expect(node1.attributes[HeadingBlockKeys.level], 1);
|
||||
|
||||
final node2 = editorState.document.nodeAtPath([1])!;
|
||||
expect(node2.type, ParagraphBlockKeys.type);
|
||||
expect(node2.delta!.toPlainText(), 'item 1');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _insertToggleHeadingBlockInDocument(
|
||||
WidgetTester tester,
|
||||
int level,
|
||||
) async {
|
||||
final name = switch (level) {
|
||||
1 => LocaleKeys.document_slashMenu_name_toggleHeading1.tr(),
|
||||
2 => LocaleKeys.document_slashMenu_name_toggleHeading2.tr(),
|
||||
3 => LocaleKeys.document_slashMenu_name_toggleHeading3.tr(),
|
||||
_ => throw Exception('Invalid level: $level'),
|
||||
};
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
name,
|
||||
offset: 150,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.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';
|
||||
|
@ -214,5 +216,73 @@ void main() {
|
|||
|
||||
expectToggleListOpened();
|
||||
});
|
||||
|
||||
Future<void> prepareToggleHeadingBlock(
|
||||
WidgetTester tester,
|
||||
String text,
|
||||
) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText(text);
|
||||
}
|
||||
|
||||
testWidgets('> + # to toggle heading 1 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '> # Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 1);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
|
||||
testWidgets('> + ### to toggle heading 3 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '> ### Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 3);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
|
||||
testWidgets('# + > to toggle heading 1 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '# > Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 1);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
|
||||
testWidgets('### + > to toggle heading 3 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '### > Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 3);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
|
||||
testWidgets('click the toggle list to create a new paragraph',
|
||||
(tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '> # Hello');
|
||||
final emptyHintText = find.text(
|
||||
LocaleKeys.document_plugins_emptyToggleHeading.tr(
|
||||
args: ['1'],
|
||||
),
|
||||
);
|
||||
expect(emptyHintText, findsOneWidget);
|
||||
|
||||
await tester.tapButton(emptyHintText);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the new paragraph is created
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0, 0])!;
|
||||
expect(node.type, ParagraphBlockKeys.type);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ void main() {
|
|||
testWidgets('empty test', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.wait(1000);
|
||||
await tester.wait(500);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -208,7 +208,6 @@ void main() {
|
|||
expect(find.text('100'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
// TODO: Uncmoment expects
|
||||
testWidgets('Calculations count + count empty w/ filter', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'grid_edit_row_test.dart' as grid_edit_row_test_runner;
|
||||
import 'grid_filter_and_sort_test.dart' as grid_filter_and_sort_test_runner;
|
||||
import 'grid_reopen_test.dart' as grid_reopen_test_runner;
|
||||
import 'grid_reorder_row_test.dart' as grid_reorder_row_test_runner;
|
||||
import 'grid_row_test.dart' as grid_row_test_runner;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
grid_reopen_test_runner.main();
|
||||
grid_row_test_runner.main();
|
||||
grid_reorder_row_test_runner.main();
|
||||
grid_filter_and_sort_test_runner.main();
|
||||
grid_edit_row_test_runner.main();
|
||||
// grid_calculations_test_runner.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -10,7 +10,6 @@ import 'package:integration_test/integration_test.dart';
|
|||
|
||||
import '../../shared/keyboard.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
|
@ -82,7 +82,7 @@ void main() {
|
|||
// reset to appflowy cloud
|
||||
await tester.tapButton(
|
||||
findServerType(AuthenticatorType.appflowyCloudSelfHost),
|
||||
);
|
||||
);
|
||||
// change the server type to appflowy cloud
|
||||
await tester.tapButton(
|
||||
findServerType(AuthenticatorType.appflowyCloud),
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.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';
|
||||
|
@ -9,75 +12,158 @@ import '../../shared/expectation.dart';
|
|||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
const emoji = '😁';
|
||||
final emoji = EmojiIconData.emoji('😁');
|
||||
|
||||
group('Icon:', () {
|
||||
testWidgets('Update page icon in sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
Future<EmojiIconData> loadIcon() async {
|
||||
await loadIconGroups();
|
||||
final groups = kIconGroups!;
|
||||
final firstGroup = groups.first;
|
||||
final firstIcon = firstGroup.icons.first;
|
||||
return EmojiIconData.icon(
|
||||
IconsData(
|
||||
firstGroup.name,
|
||||
firstIcon.content,
|
||||
firstIcon.name,
|
||||
builtInSpaceColors.first,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
);
|
||||
testWidgets('Update page emoji in sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// update its icon
|
||||
await tester.updatePageIconInSidebarByName(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
icon: emoji,
|
||||
);
|
||||
|
||||
tester.expectViewHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
emoji,
|
||||
);
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
);
|
||||
|
||||
testWidgets('Update page icon in title bar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
// update its emoji
|
||||
await tester.updatePageIconInSidebarByName(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
icon: emoji,
|
||||
);
|
||||
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
tester.expectViewHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
emoji,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
);
|
||||
testWidgets('Update page emoji in title bar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// update its icon
|
||||
await tester.updatePageIconInTitleBarByName(
|
||||
name: value.name,
|
||||
layout: value,
|
||||
icon: emoji,
|
||||
);
|
||||
|
||||
tester.expectViewHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
emoji,
|
||||
);
|
||||
|
||||
tester.expectViewTitleHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
emoji,
|
||||
);
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
);
|
||||
|
||||
// update its emoji
|
||||
await tester.updatePageIconInTitleBarByName(
|
||||
name: value.name,
|
||||
layout: value,
|
||||
icon: emoji,
|
||||
);
|
||||
|
||||
tester.expectViewHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
emoji,
|
||||
);
|
||||
|
||||
tester.expectViewTitleHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
emoji,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Update page icon in sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
final iconData = await loadIcon();
|
||||
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
);
|
||||
|
||||
// update its icon
|
||||
await tester.updatePageIconInSidebarByName(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
icon: iconData,
|
||||
);
|
||||
|
||||
tester.expectViewHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
iconData,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Update page icon in title bar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
final iconData = await loadIcon();
|
||||
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
layout: value,
|
||||
);
|
||||
|
||||
// update its icon
|
||||
await tester.updatePageIconInTitleBarByName(
|
||||
name: value.name,
|
||||
layout: value,
|
||||
icon: iconData,
|
||||
);
|
||||
|
||||
tester.expectViewHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
iconData,
|
||||
);
|
||||
|
||||
tester.expectViewTitleHasIcon(
|
||||
value.name,
|
||||
value,
|
||||
iconData,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -198,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();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_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.hoverOnWidget(
|
||||
find.byType(ViewItem),
|
||||
onHover: () async {
|
||||
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,
|
||||
EmojiIconData.emoji(emoji),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.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 '../../shared/base.dart';
|
||||
import '../../shared/common_operations.dart';
|
||||
import '../../shared/document_test_operations.dart';
|
||||
import '../document/document_codeblock_paste_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('Code Block Language Selector Test', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
/// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
/// tap editor to get focus
|
||||
await tester.tapButton(find.byType(AppFlowyEditor));
|
||||
|
||||
expect(find.byType(CodeBlockLanguageSelector), findsNothing);
|
||||
await insertCodeBlockInDocument(tester);
|
||||
|
||||
///tap button
|
||||
await tester.hoverOnWidget(find.byType(CodeBlockComponentWidget));
|
||||
await tester
|
||||
.tapButtonWithName(LocaleKeys.document_codeBlock_language_auto.tr());
|
||||
expect(find.byType(CodeBlockLanguageSelector), findsOneWidget);
|
||||
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
await onKey(tester, LogicalKeyboardKey.arrowDown);
|
||||
}
|
||||
for (var i = 0; i < 2; ++i) {
|
||||
await onKey(tester, LogicalKeyboardKey.arrowUp);
|
||||
}
|
||||
|
||||
await onKey(tester, LogicalKeyboardKey.enter);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
String language = editorState
|
||||
.getNodeAtPath([0])!
|
||||
.attributes[CodeBlockKeys.language]
|
||||
.toString();
|
||||
expect(
|
||||
language.toLowerCase(),
|
||||
defaultCodeBlockSupportedLanguages.first.toLowerCase(),
|
||||
);
|
||||
|
||||
await tester.hoverOnWidget(find.byType(CodeBlockComponentWidget));
|
||||
await tester.tapButtonWithName(language);
|
||||
|
||||
await onKey(tester, LogicalKeyboardKey.arrowUp);
|
||||
await onKey(tester, LogicalKeyboardKey.enter);
|
||||
|
||||
language = editorState
|
||||
.getNodeAtPath([0])!
|
||||
.attributes[CodeBlockKeys.language]
|
||||
.toString();
|
||||
expect(
|
||||
language.toLowerCase(),
|
||||
defaultCodeBlockSupportedLanguages.last.toLowerCase(),
|
||||
);
|
||||
|
||||
await tester.hoverOnWidget(find.byType(CodeBlockComponentWidget));
|
||||
await tester.tapButtonWithName(language);
|
||||
tester.testTextInput.enterText("rust");
|
||||
await onKey(tester, LogicalKeyboardKey.delete);
|
||||
await onKey(tester, LogicalKeyboardKey.delete);
|
||||
await onKey(tester, LogicalKeyboardKey.arrowDown);
|
||||
tester.testTextInput.enterText("st");
|
||||
await onKey(tester, LogicalKeyboardKey.arrowDown);
|
||||
await onKey(tester, LogicalKeyboardKey.enter);
|
||||
language = editorState
|
||||
.getNodeAtPath([0])!
|
||||
.attributes[CodeBlockKeys.language]
|
||||
.toString();
|
||||
expect(language.toLowerCase(), 'rust');
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> onKey(WidgetTester tester, LogicalKeyboardKey key) async {
|
||||
await tester.simulateKeyEvent(key);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
@ -87,7 +88,7 @@ void main() {
|
|||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
TableBlockKeys.type,
|
||||
SimpleTableBlockKeys.type,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/gestures.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/base.dart';
|
||||
import '../../shared/common_operations.dart';
|
||||
import '../../shared/expectation.dart';
|
||||
import '../../shared/keyboard.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
const _documentName = 'First Doc';
|
||||
const _documentTwoName = 'Second Doc';
|
||||
|
@ -20,17 +22,12 @@ void main() {
|
|||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Tabs', () {
|
||||
testWidgets('Open AppFlowy and open/navigate/close tabs', (tester) async {
|
||||
testWidgets('open/navigate/close tabs', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(TabBar),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
// No tabs rendered yet
|
||||
expect(find.byType(FlowyTab), findsNothing);
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentName);
|
||||
|
||||
|
@ -44,7 +41,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabBar),
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
findsNWidgets(3),
|
||||
|
@ -71,11 +68,267 @@ void main() {
|
|||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabBar),
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('right click show tab menu, close others', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(TabBar),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentName);
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentTwoName);
|
||||
|
||||
/// Open second menu item in a new tab
|
||||
await tester.openAppInNewTab(gettingStarted, ViewLayoutPB.Document);
|
||||
|
||||
/// Open third menu item in a new tab
|
||||
await tester.openAppInNewTab(_documentName, ViewLayoutPB.Document);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
|
||||
/// Right click on second tab
|
||||
await tester.tap(
|
||||
buttons: kSecondaryButton,
|
||||
find.descendant(
|
||||
of: find.byType(FlowyTab),
|
||||
matching: find.text(gettingStarted),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(TabMenu), findsOneWidget);
|
||||
|
||||
final firstTabFinder = find.descendant(
|
||||
of: find.byType(FlowyTab),
|
||||
matching: find.text(_documentTwoName),
|
||||
);
|
||||
final secondTabFinder = find.descendant(
|
||||
of: find.byType(FlowyTab),
|
||||
matching: find.text(gettingStarted),
|
||||
);
|
||||
final thirdTabFinder = find.descendant(
|
||||
of: find.byType(FlowyTab),
|
||||
matching: find.text(_documentName),
|
||||
);
|
||||
|
||||
expect(firstTabFinder, findsOneWidget);
|
||||
expect(secondTabFinder, findsOneWidget);
|
||||
expect(thirdTabFinder, findsOneWidget);
|
||||
|
||||
// Close other tabs than the second item
|
||||
await tester.tap(find.text(LocaleKeys.tabMenu_closeOthers.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// We expect to not find any tabs
|
||||
expect(firstTabFinder, findsNothing);
|
||||
expect(secondTabFinder, findsNothing);
|
||||
expect(thirdTabFinder, findsNothing);
|
||||
|
||||
// Expect second tab to be current page (current page has breadcrumb, cover title,
|
||||
// and in this case view name in sidebar)
|
||||
expect(find.text(gettingStarted), findsNWidgets(3));
|
||||
});
|
||||
|
||||
testWidgets('cannot close pinned tabs', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(TabBar),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentName);
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentTwoName);
|
||||
|
||||
// Open second menu item in a new tab
|
||||
await tester.openAppInNewTab(gettingStarted, ViewLayoutPB.Document);
|
||||
|
||||
// Open third menu item in a new tab
|
||||
await tester.openAppInNewTab(_documentName, ViewLayoutPB.Document);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
|
||||
const firstTab = _documentTwoName;
|
||||
const secondTab = gettingStarted;
|
||||
const thirdTab = _documentName;
|
||||
|
||||
expect(tester.isTabAtIndex(firstTab, 0), isTrue);
|
||||
expect(tester.isTabAtIndex(secondTab, 1), isTrue);
|
||||
expect(tester.isTabAtIndex(thirdTab, 2), isTrue);
|
||||
|
||||
expect(tester.isTabPinned(gettingStarted), isFalse);
|
||||
|
||||
// Right click on second tab
|
||||
await tester.openTabMenu(gettingStarted);
|
||||
expect(find.byType(TabMenu), findsOneWidget);
|
||||
|
||||
// Pin second tab
|
||||
await tester.tap(find.text(LocaleKeys.tabMenu_pinTab.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.isTabPinned(gettingStarted), isTrue);
|
||||
|
||||
/// Right click on first unpinned tab (second tab)
|
||||
await tester.openTabMenu(_documentTwoName);
|
||||
|
||||
// Close others
|
||||
await tester.tap(find.text(LocaleKeys.tabMenu_closeOthers.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// We expect to find 2 tabs, the first pinned tab and the second tab
|
||||
expect(find.byType(FlowyTab), findsNWidgets(2));
|
||||
expect(tester.isTabAtIndex(gettingStarted, 0), isTrue);
|
||||
expect(tester.isTabAtIndex(_documentTwoName, 1), isTrue);
|
||||
});
|
||||
|
||||
testWidgets('pin/unpin tabs proper order', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(TabBar),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentName);
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentTwoName);
|
||||
|
||||
// Open second menu item in a new tab
|
||||
await tester.openAppInNewTab(gettingStarted, ViewLayoutPB.Document);
|
||||
|
||||
// Open third menu item in a new tab
|
||||
await tester.openAppInNewTab(_documentName, ViewLayoutPB.Document);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
|
||||
const firstTabName = _documentTwoName;
|
||||
const secondTabName = gettingStarted;
|
||||
const thirdTabName = _documentName;
|
||||
|
||||
// Expect correct order
|
||||
expect(tester.isTabAtIndex(firstTabName, 0), isTrue);
|
||||
expect(tester.isTabAtIndex(secondTabName, 1), isTrue);
|
||||
expect(tester.isTabAtIndex(thirdTabName, 2), isTrue);
|
||||
|
||||
// Pin second tab
|
||||
await tester.openTabMenu(secondTabName);
|
||||
await tester.tap(find.text(LocaleKeys.tabMenu_pinTab.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.isTabPinned(secondTabName), isTrue);
|
||||
|
||||
// Expect correct order
|
||||
expect(tester.isTabAtIndex(secondTabName, 0), isTrue);
|
||||
expect(tester.isTabAtIndex(firstTabName, 1), isTrue);
|
||||
expect(tester.isTabAtIndex(thirdTabName, 2), isTrue);
|
||||
|
||||
// Pin new second tab (first tab)
|
||||
await tester.openTabMenu(firstTabName);
|
||||
await tester.tap(find.text(LocaleKeys.tabMenu_pinTab.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.isTabPinned(firstTabName), isTrue);
|
||||
expect(tester.isTabPinned(secondTabName), isTrue);
|
||||
expect(tester.isTabPinned(thirdTabName), isFalse);
|
||||
|
||||
expect(tester.isTabAtIndex(secondTabName, 0), isTrue);
|
||||
expect(tester.isTabAtIndex(firstTabName, 1), isTrue);
|
||||
expect(tester.isTabAtIndex(thirdTabName, 2), isTrue);
|
||||
|
||||
// Unpin second tab
|
||||
await tester.openTabMenu(secondTabName);
|
||||
await tester.tap(find.text(LocaleKeys.tabMenu_unpinTab.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.isTabPinned(firstTabName), isTrue);
|
||||
expect(tester.isTabPinned(secondTabName), isFalse);
|
||||
expect(tester.isTabPinned(thirdTabName), isFalse);
|
||||
|
||||
expect(tester.isTabAtIndex(firstTabName, 0), isTrue);
|
||||
expect(tester.isTabAtIndex(secondTabName, 1), isTrue);
|
||||
expect(tester.isTabAtIndex(thirdTabName, 2), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension _TabsTester on WidgetTester {
|
||||
bool isTabPinned(String tabName) {
|
||||
final tabFinder = find.ancestor(
|
||||
of: find.byWidgetPredicate(
|
||||
(w) => w is ViewTabBarItem && w.view.name == tabName,
|
||||
),
|
||||
matching: find.byType(FlowyTab),
|
||||
);
|
||||
|
||||
final FlowyTab tabWidget = widget(tabFinder);
|
||||
return tabWidget.pageManager.isPinned;
|
||||
}
|
||||
|
||||
bool isTabAtIndex(String tabName, int index) {
|
||||
final tabFinder = find.ancestor(
|
||||
of: find.byWidgetPredicate(
|
||||
(w) => w is ViewTabBarItem && w.view.name == tabName,
|
||||
),
|
||||
matching: find.byType(FlowyTab),
|
||||
);
|
||||
|
||||
final pluginId = (widget(tabFinder) as FlowyTab).pageManager.plugin.id;
|
||||
|
||||
final pluginIds = find
|
||||
.byType(FlowyTab)
|
||||
.evaluate()
|
||||
.map((e) => (e.widget as FlowyTab).pageManager.plugin.id);
|
||||
|
||||
return pluginIds.elementAt(index) == pluginId;
|
||||
}
|
||||
|
||||
Future<void> openTabMenu(String tabName) async {
|
||||
await tap(
|
||||
buttons: kSecondaryButton,
|
||||
find.ancestor(
|
||||
of: find.byWidgetPredicate(
|
||||
(w) => w is ViewTabBarItem && w.view.name == tabName,
|
||||
),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'emoji_shortcut_test.dart' as emoji_shortcut_test;
|
||||
import 'hotkeys_test.dart' as hotkeys_test;
|
||||
import 'import_files_test.dart' as import_files_test;
|
||||
import 'share_markdown_test.dart' as share_markdown_test;
|
||||
import 'zoom_in_out_test.dart' as zoom_in_out_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
hotkeys_test.main();
|
||||
emoji_shortcut_test.main();
|
||||
hotkeys_test.main();
|
||||
emoji_shortcut_test.main();
|
||||
share_markdown_test.main();
|
||||
import_files_test.main();
|
||||
zoom_in_out_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner_1.dart' as document_test_runner;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/document/document_test_runner_1.dart' as document_test_runner_1;
|
||||
import 'desktop/uncategorized/switch_folder_test.dart' as switch_folder_test;
|
||||
|
||||
Future<void> main() async {
|
||||
|
@ -11,11 +10,7 @@ Future<void> main() async {
|
|||
Future<void> runIntegration1OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
first_test.main();
|
||||
|
||||
switch_folder_test.main();
|
||||
document_test_runner.main();
|
||||
|
||||
document_test_runner_1.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
|
|
|
@ -1,18 +1,7 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/database/database_calendar_test.dart' as database_calendar_test;
|
||||
import 'desktop/database/database_cell_test.dart' as database_cell_test;
|
||||
import 'desktop/database/database_field_settings_test.dart'
|
||||
as database_field_settings_test;
|
||||
import 'desktop/database/database_field_test.dart' as database_field_test;
|
||||
import 'desktop/database/database_filter_test.dart' as database_filter_test;
|
||||
import 'desktop/database/database_media_test.dart' as database_media_test;
|
||||
import 'desktop/database/database_row_page_test.dart' as database_row_page_test;
|
||||
import 'desktop/database/database_setting_test.dart' as database_setting_test;
|
||||
import 'desktop/database/database_share_test.dart' as database_share_test;
|
||||
import 'desktop/database/database_sort_test.dart' as database_sort_test;
|
||||
import 'desktop/database/database_view_test.dart' as database_view_test;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/database/database_test_runner_1.dart' as database_test_runner_1;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration2OnDesktop();
|
||||
|
@ -21,20 +10,8 @@ Future<void> main() async {
|
|||
Future<void> runIntegration2OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
first_test.main();
|
||||
|
||||
database_cell_test.main();
|
||||
database_field_test.main();
|
||||
database_field_settings_test.main();
|
||||
database_share_test.main();
|
||||
database_row_page_test.main();
|
||||
database_setting_test.main();
|
||||
database_filter_test.main();
|
||||
database_sort_test.main();
|
||||
database_view_test.main();
|
||||
database_calendar_test.main();
|
||||
database_media_test.main();
|
||||
|
||||
database_test_runner_1.main();
|
||||
// DON'T add more tests here. This is the second test runner for desktop.
|
||||
}
|
||||
|
|
|
@ -1,24 +1,8 @@
|
|||
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_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;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/uncategorized/hotkeys_test.dart' as hotkeys_test;
|
||||
import 'desktop/uncategorized/import_files_test.dart' as import_files_test;
|
||||
import 'desktop/uncategorized/share_markdown_test.dart' as share_markdown_test;
|
||||
import 'desktop/uncategorized/tabs_test.dart' as tabs_test;
|
||||
import 'desktop/uncategorized/zoom_in_out_test.dart' as zoom_in_out_test;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
import 'desktop/grid/grid_test_runner_1.dart' as grid_test_runner_1;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration3OnDesktop();
|
||||
|
@ -27,26 +11,9 @@ Future<void> main() async {
|
|||
Future<void> runIntegration3OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
first_test.main();
|
||||
|
||||
hotkeys_test.main();
|
||||
emoji_shortcut_test.main();
|
||||
hotkeys_test.main();
|
||||
emoji_shortcut_test.main();
|
||||
settings_test_runner.main();
|
||||
share_markdown_test.main();
|
||||
import_files_test.main();
|
||||
sidebar_test_runner.main();
|
||||
board_test_runner.main();
|
||||
tabs_test.main();
|
||||
database_row_cover_test.main();
|
||||
grid_reopen_test_runner.main();
|
||||
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
|
||||
grid_test_runner_1.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:integration_test/integration_test.dart';
|
|||
|
||||
import 'desktop/document/document_test_runner_2.dart' as document_test_runner_2;
|
||||
import 'desktop/grid/grid_calculations_test.dart' as grid_calculations_test;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration4OnDesktop();
|
||||
|
@ -11,9 +11,9 @@ Future<void> main() async {
|
|||
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();
|
||||
grid_calculations_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/database/database_test_runner_2.dart' as database_test_runner_2;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration5OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration5OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
database_test_runner_2.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
import 'desktop/settings/settings_runner.dart' as settings_test_runner;
|
||||
import 'desktop/sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||
import 'desktop/uncategorized/uncategorized_test_runner_1.dart'
|
||||
as uncategorized_test_runner_1;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration6OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration6OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
settings_test_runner.main();
|
||||
sidebar_test_runner.main();
|
||||
uncategorized_test_runner_1.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner_3.dart' as document_test_runner_3;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration7OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration7OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
document_test_runner_3.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner_4.dart' as document_test_runner_4;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration8OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration8OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
document_test_runner_4.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/uncategorized/tabs_test.dart' as tabs_test;
|
||||
import 'desktop/uncategorized/code_block_language_selector_test.dart'
|
||||
as code_language_selector;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration9OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration9OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
tabs_test.main();
|
||||
code_language_selector.main();
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
import 'document/publish_test.dart' as publish_test;
|
||||
import 'document/share_link_test.dart' as share_link_test;
|
||||
import 'space/space_test.dart' as space_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();
|
||||
space_test.main();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/workspaces/create_workspace_menu.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('publish:', () {
|
||||
testWidgets('''
|
||||
1. publish document
|
||||
2. update path name
|
||||
3. unpublish 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,
|
||||
);
|
||||
|
||||
// update the path name
|
||||
await tester.editor.clickMoreActionItemOnMobile(
|
||||
LocaleKeys.shareAction_updatePathName.tr(),
|
||||
);
|
||||
|
||||
const pathName1 = '???????????????';
|
||||
const pathName2 = 'AppFlowy';
|
||||
|
||||
final textField = find.descendant(
|
||||
of: find.byType(EditWorkspaceNameBottomSheet),
|
||||
matching: find.byType(TextFormField),
|
||||
);
|
||||
await tester.enterText(textField, pathName1);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// wait 50ms to ensure the error message is shown
|
||||
await tester.wait(50);
|
||||
|
||||
// click the confirm button
|
||||
final confirmButton = find.text(LocaleKeys.button_confirm.tr());
|
||||
await tester.tapButton(confirmButton);
|
||||
|
||||
// expect to see the update path name failed toast
|
||||
final updatePathFailedText = find.text(
|
||||
LocaleKeys.settings_sites_error_publishNameContainsInvalidCharacters
|
||||
.tr(),
|
||||
);
|
||||
expect(updatePathFailedText, findsOneWidget);
|
||||
|
||||
// input the valid path name
|
||||
await tester.enterText(textField, pathName2);
|
||||
await tester.pumpAndSettle();
|
||||
// click the confirm button
|
||||
await tester.tapButton(confirmButton);
|
||||
|
||||
// wait 50ms to ensure the error message is shown
|
||||
await tester.wait(50);
|
||||
|
||||
// expect to see the update path name success toast
|
||||
final updatePathSuccessText = find.findTextInFlowyText(
|
||||
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
|
||||
);
|
||||
expect(updatePathSuccessText, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(updatePathSuccessText);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,39 +1,12 @@
|
|||
// 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() {
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
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/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/manage_space_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/space_menu_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/workspaces/create_workspace_menu.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/cupertino.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('space operations:', () {
|
||||
Future<void> openSpaceMenu(WidgetTester tester) async {
|
||||
final spaceHeader = find.byType(MobileSpaceHeader);
|
||||
await tester.tapButton(spaceHeader);
|
||||
await tester.pumpUntilFound(find.byType(MobileSpaceMenu));
|
||||
}
|
||||
|
||||
Future<void> openSpaceMenuMoreOptions(
|
||||
WidgetTester tester,
|
||||
ViewPB space,
|
||||
) async {
|
||||
final spaceMenuItemTrailing = find.byWidgetPredicate(
|
||||
(w) => w is SpaceMenuItemTrailing && w.space.id == space.id,
|
||||
);
|
||||
final moreOptions = find.descendant(
|
||||
of: spaceMenuItemTrailing,
|
||||
matching: find.byWidgetPredicate(
|
||||
(w) =>
|
||||
w is FlowySvg &&
|
||||
w.svg.path == FlowySvgs.workspace_three_dots_s.path,
|
||||
),
|
||||
);
|
||||
await tester.tapButton(moreOptions);
|
||||
await tester.pumpUntilFound(find.byType(SpaceMenuMoreOptions));
|
||||
}
|
||||
|
||||
// combine the tests together to reduce the CI time
|
||||
testWidgets('''
|
||||
1. create a new space
|
||||
2. update the space name
|
||||
3. update the space permission
|
||||
4. update the space icon
|
||||
5. delete the space
|
||||
''', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// 1. create a new space
|
||||
// click the space menu
|
||||
await openSpaceMenu(tester);
|
||||
|
||||
// click the create a new space button
|
||||
final createNewSpaceButton = find.text(
|
||||
LocaleKeys.space_createNewSpace.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(createNewSpaceButton);
|
||||
await tester.tapButton(createNewSpaceButton);
|
||||
|
||||
// input the new space name
|
||||
final inputField = find.descendant(
|
||||
of: find.byType(ManageSpaceWidget),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
const newSpaceName = 'AppFlowy';
|
||||
await tester.enterText(inputField, newSpaceName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// change the space permission to private
|
||||
final permissionOption = find.byType(ManageSpacePermissionOption);
|
||||
await tester.tapButton(permissionOption);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final privateOption = find.text(LocaleKeys.space_privatePermission.tr());
|
||||
await tester.tapButton(privateOption);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// change the space icon color
|
||||
final color = builtInSpaceColors[1];
|
||||
final iconOption = find.descendant(
|
||||
of: find.byType(ManageSpaceIconOption),
|
||||
matching: find.byWidgetPredicate(
|
||||
(w) => w is SpaceColorItem && w.color == color,
|
||||
),
|
||||
);
|
||||
await tester.tapButton(iconOption);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// change the space icon
|
||||
final icon = kIconGroups![0].icons[1];
|
||||
final iconItem = find.descendant(
|
||||
of: find.byType(ManageSpaceIconOption),
|
||||
matching: find.byWidgetPredicate(
|
||||
(w) => w is SpaceIconItem && w.icon == icon,
|
||||
),
|
||||
);
|
||||
await tester.tapButton(iconItem);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the done button
|
||||
final doneButton = find.descendant(
|
||||
of: find.byWidgetPredicate(
|
||||
(w) =>
|
||||
w is BottomSheetHeader &&
|
||||
w.title == LocaleKeys.space_createSpace.tr(),
|
||||
),
|
||||
matching: find.text(LocaleKeys.button_done.tr()),
|
||||
);
|
||||
await tester.tapButton(doneButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// wait 100ms for the space to be created
|
||||
await tester.wait(100);
|
||||
|
||||
// verify the space is created
|
||||
await openSpaceMenu(tester);
|
||||
final spaceItems = find.byType(MobileSpaceMenuItem);
|
||||
// expect to see 3 space items, 2 are built-in, 1 is the new space
|
||||
expect(spaceItems, findsNWidgets(3));
|
||||
// convert the space item to a widget
|
||||
final spaceWidget =
|
||||
tester.widgetList<MobileSpaceMenuItem>(spaceItems).last;
|
||||
final space = spaceWidget.space;
|
||||
expect(space.name, newSpaceName);
|
||||
expect(space.spacePermission, SpacePermission.private);
|
||||
expect(space.spaceIcon, icon.iconPath);
|
||||
expect(space.spaceIconColor, color);
|
||||
|
||||
// open the SpaceMenuMoreOptions menu
|
||||
await openSpaceMenuMoreOptions(tester, space);
|
||||
|
||||
// 2. rename the space name
|
||||
final renameOption = find.text(LocaleKeys.button_rename.tr());
|
||||
await tester.tapButton(renameOption);
|
||||
await tester.pumpUntilFound(find.byType(EditWorkspaceNameBottomSheet));
|
||||
|
||||
// input the new space name
|
||||
final renameInputField = find.descendant(
|
||||
of: find.byType(EditWorkspaceNameBottomSheet),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
const renameSpaceName = 'HelloWorld';
|
||||
await tester.enterText(renameInputField, renameSpaceName);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapButton(find.text(LocaleKeys.button_confirm.tr()));
|
||||
|
||||
// click the done button
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final renameSuccess = find.text(
|
||||
LocaleKeys.space_success_renameSpace.tr(),
|
||||
);
|
||||
await tester.pumpUntilNotFound(renameSuccess);
|
||||
|
||||
// check the space name is updated
|
||||
await openSpaceMenu(tester);
|
||||
final renameSpaceItem = find.descendant(
|
||||
of: find.byType(MobileSpaceMenuItem),
|
||||
matching: find.text(renameSpaceName),
|
||||
);
|
||||
expect(renameSpaceItem, findsOneWidget);
|
||||
|
||||
// 3. manage the space
|
||||
await openSpaceMenuMoreOptions(tester, space);
|
||||
|
||||
final manageOption = find.text(LocaleKeys.space_manage.tr());
|
||||
await tester.tapButton(manageOption);
|
||||
await tester.pumpUntilFound(find.byType(ManageSpaceWidget));
|
||||
|
||||
// 3.1 rename the space
|
||||
final textField = find.descendant(
|
||||
of: find.byType(ManageSpaceWidget),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await tester.enterText(textField, 'AppFlowy');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 3.2 change the permission
|
||||
final permissionOption2 = find.byType(ManageSpacePermissionOption);
|
||||
await tester.tapButton(permissionOption2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final publicOption = find.text(LocaleKeys.space_publicPermission.tr());
|
||||
await tester.tapButton(publicOption);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 3.3 change the icon
|
||||
// change the space icon color
|
||||
final color2 = builtInSpaceColors[2];
|
||||
final iconOption2 = find.descendant(
|
||||
of: find.byType(ManageSpaceIconOption),
|
||||
matching: find.byWidgetPredicate(
|
||||
(w) => w is SpaceColorItem && w.color == color2,
|
||||
),
|
||||
);
|
||||
await tester.tapButton(iconOption2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// change the space icon
|
||||
final icon2 = kIconGroups![0].icons[2];
|
||||
final iconItem2 = find.descendant(
|
||||
of: find.byType(ManageSpaceIconOption),
|
||||
matching: find.byWidgetPredicate(
|
||||
(w) => w is SpaceIconItem && w.icon == icon2,
|
||||
),
|
||||
);
|
||||
await tester.tapButton(iconItem2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the done button
|
||||
final doneButton2 = find.descendant(
|
||||
of: find.byWidgetPredicate(
|
||||
(w) =>
|
||||
w is BottomSheetHeader &&
|
||||
w.title == LocaleKeys.space_manageSpace.tr(),
|
||||
),
|
||||
matching: find.text(LocaleKeys.button_done.tr()),
|
||||
);
|
||||
await tester.tapButton(doneButton2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the space is updated
|
||||
final spaceItems2 = find.byType(MobileSpaceMenuItem);
|
||||
final spaceWidget2 =
|
||||
tester.widgetList<MobileSpaceMenuItem>(spaceItems2).last;
|
||||
final space2 = spaceWidget2.space;
|
||||
expect(space2.name, 'AppFlowy');
|
||||
expect(space2.spacePermission, SpacePermission.publicToAll);
|
||||
expect(space2.spaceIcon, icon2.iconPath);
|
||||
expect(space2.spaceIconColor, color2);
|
||||
final manageSuccess = find.text(
|
||||
LocaleKeys.space_success_updateSpace.tr(),
|
||||
);
|
||||
await tester.pumpUntilNotFound(manageSuccess);
|
||||
|
||||
// 4. duplicate the space
|
||||
await openSpaceMenuMoreOptions(tester, space);
|
||||
final duplicateOption = find.text(LocaleKeys.space_duplicate.tr());
|
||||
await tester.tapButton(duplicateOption);
|
||||
final duplicateSuccess = find.text(
|
||||
LocaleKeys.space_success_duplicateSpace.tr(),
|
||||
);
|
||||
await tester.pumpUntilNotFound(duplicateSuccess);
|
||||
|
||||
// check the space is duplicated
|
||||
await openSpaceMenu(tester);
|
||||
final spaceItems3 = find.byType(MobileSpaceMenuItem);
|
||||
expect(spaceItems3, findsNWidgets(4));
|
||||
|
||||
// 5. delete the space
|
||||
await openSpaceMenuMoreOptions(tester, space);
|
||||
final deleteOption = find.text(LocaleKeys.button_delete.tr());
|
||||
await tester.tapButton(deleteOption);
|
||||
final confirmDeleteButton = find.descendant(
|
||||
of: find.byType(CupertinoDialogAction),
|
||||
matching: find.text(LocaleKeys.button_delete.tr()),
|
||||
);
|
||||
await tester.tapButton(confirmDeleteButton);
|
||||
final deleteSuccess = find.text(
|
||||
LocaleKeys.space_success_deleteSpace.tr(),
|
||||
);
|
||||
await tester.pumpUntilNotFound(deleteSuccess);
|
||||
|
||||
// check the space is deleted
|
||||
final spaceItems4 = find.byType(MobileSpaceMenuItem);
|
||||
expect(spaceItems4, findsNWidgets(3));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,37 +1,11 @@
|
|||
// 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() {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'page_style_test.dart' as page_style_test;
|
||||
import 'plus_menu_test.dart' as plus_menu_test;
|
||||
import 'simple_table_test.dart' as simple_table_test;
|
||||
import 'title_test.dart' as title_test;
|
||||
|
||||
void main() {
|
||||
|
@ -9,4 +11,6 @@ void main() {
|
|||
// Document integration tests
|
||||
title_test.main();
|
||||
page_style_test.main();
|
||||
plus_menu_test.main();
|
||||
simple_table_test.main();
|
||||
}
|
||||
|
|
|
@ -1,42 +1,19 @@
|
|||
// 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/dir.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document page style', () {
|
||||
group('document page style:', () {
|
||||
double getCurrentEditorFontSize() {
|
||||
final editorPage = find
|
||||
.byType(AppFlowyEditorPage)
|
||||
|
@ -59,8 +36,7 @@ void main() {
|
|||
return editorPage.styleCustomizer
|
||||
.style()
|
||||
.textStyleConfiguration
|
||||
.text
|
||||
.height!;
|
||||
.lineHeight;
|
||||
}
|
||||
|
||||
testWidgets('change font size in page style settings', (tester) async {
|
||||
|
@ -86,20 +62,24 @@ void main() {
|
|||
await tester.openPage(gettingStarted);
|
||||
// click the layout button
|
||||
await tester.tapButton(find.byType(MobileViewPageLayoutButton));
|
||||
var lineHeight = getCurrentEditorLineHeight();
|
||||
expect(
|
||||
getCurrentEditorLineHeight(),
|
||||
lineHeight,
|
||||
PageStyleLineHeightLayout.normal.lineHeight,
|
||||
);
|
||||
// change line height from normal to large
|
||||
await tester.tapSvgButton(FlowySvgs.m_layout_large_s);
|
||||
await tester.pumpAndSettle();
|
||||
lineHeight = getCurrentEditorLineHeight();
|
||||
expect(
|
||||
getCurrentEditorLineHeight(),
|
||||
lineHeight,
|
||||
PageStyleLineHeightLayout.large.lineHeight,
|
||||
);
|
||||
// change line height from large to small
|
||||
await tester.tapSvgButton(FlowySvgs.m_layout_small_s);
|
||||
lineHeight = getCurrentEditorLineHeight();
|
||||
expect(
|
||||
getCurrentEditorLineHeight(),
|
||||
lineHeight,
|
||||
PageStyleLineHeightLayout.small.lineHeight,
|
||||
);
|
||||
});
|
||||
|
@ -126,7 +106,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,89 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.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/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document plus menu:', () {
|
||||
testWidgets('add the toggle heading blocks via plus menu', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
await tester.createNewDocumentOnMobile('toggle heading blocks');
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
// focus on the editor
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// open the plus menu and select the toggle heading block
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_toggleHeading1.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final block1 = editorState.getNodeAtPath([0])!;
|
||||
expect(block1.type, equals(ToggleListBlockKeys.type));
|
||||
expect(block1.attributes[ToggleListBlockKeys.level], equals(1));
|
||||
|
||||
// click the expand button won't cancel the selection
|
||||
await tester.tapButton(find.byIcon(Icons.arrow_right));
|
||||
expect(
|
||||
editorState.selection,
|
||||
equals(Selection.collapsed(Position(path: [0]))),
|
||||
);
|
||||
|
||||
// focus on the next line
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: [1])),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// open the plus menu and select the toggle heading block
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_toggleHeading2.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final block2 = editorState.getNodeAtPath([1])!;
|
||||
expect(block2.type, equals(ToggleListBlockKeys.type));
|
||||
expect(block2.attributes[ToggleListBlockKeys.level], equals(2));
|
||||
|
||||
// focus on the next line
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// open the plus menu and select the toggle heading block
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_toggleHeading3.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final block3 = editorState.getNodeAtPath([2])!;
|
||||
expect(block3.type, equals(ToggleListBlockKeys.type));
|
||||
expect(block3.attributes[ToggleListBlockKeys.level], equals(3));
|
||||
|
||||
// wait a few milliseconds to ensure the selection is updated
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
// check the selection is collapsed
|
||||
expect(
|
||||
editorState.selection,
|
||||
equals(Selection.collapsed(Position(path: [2]))),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,398 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.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';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('simple table:', () {
|
||||
testWidgets('''
|
||||
1. insert a simple table via + menu
|
||||
2. insert a row above the table
|
||||
3. insert a row below the table
|
||||
4. insert a column left to the table
|
||||
5. insert a column right to the table
|
||||
6. delete the first row
|
||||
7. delete the first column
|
||||
''', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
await tester.createNewDocumentOnMobile('simple table');
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
// focus on the editor
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final firstParagraphPath = [0, 0, 0, 0];
|
||||
|
||||
// open the plus menu and select the table block
|
||||
{
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_table.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.type, equals(SimpleTableBlockKeys.type));
|
||||
expect(table.rowLength, equals(2));
|
||||
expect(table.columnLength, equals(2));
|
||||
|
||||
// focus on the first cell
|
||||
|
||||
final selection = editorState.selection!;
|
||||
expect(selection.isCollapsed, isTrue);
|
||||
expect(selection.start.path, equals(firstParagraphPath));
|
||||
}
|
||||
|
||||
// insert left and insert right
|
||||
{
|
||||
// click the column menu button
|
||||
await tester.clickColumnMenuButton(0);
|
||||
|
||||
// insert left, insert right
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_insertLeft.tr(),
|
||||
),
|
||||
);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_insertRight
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.cancelTableActionMenu();
|
||||
|
||||
// check the table is updated
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.type, equals(SimpleTableBlockKeys.type));
|
||||
expect(table.rowLength, equals(2));
|
||||
expect(table.columnLength, equals(4));
|
||||
}
|
||||
|
||||
// insert above and insert below
|
||||
{
|
||||
// focus on the first cell
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: firstParagraphPath)),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the row menu button
|
||||
await tester.clickRowMenuButton(0);
|
||||
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_insertAbove
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_insertBelow
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
await tester.cancelTableActionMenu();
|
||||
|
||||
// check the table is updated
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.rowLength, equals(4));
|
||||
expect(table.columnLength, equals(4));
|
||||
}
|
||||
|
||||
// delete the first row
|
||||
{
|
||||
// focus on the first cell
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: firstParagraphPath)),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// delete the first row
|
||||
await tester.clickRowMenuButton(0);
|
||||
await tester.clickSimpleTableQuickAction(SimpleTableMoreAction.delete);
|
||||
await tester.cancelTableActionMenu();
|
||||
|
||||
// check the table is updated
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.rowLength, equals(3));
|
||||
expect(table.columnLength, equals(4));
|
||||
}
|
||||
|
||||
// delete the first column
|
||||
{
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: firstParagraphPath)),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.clickColumnMenuButton(0);
|
||||
await tester.clickSimpleTableQuickAction(SimpleTableMoreAction.delete);
|
||||
await tester.cancelTableActionMenu();
|
||||
|
||||
// check the table is updated
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.rowLength, equals(3));
|
||||
expect(table.columnLength, equals(3));
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('''
|
||||
1. insert a simple table via + menu
|
||||
2. enable header column
|
||||
3. enable header row
|
||||
4. set to page width
|
||||
5. distribute columns evenly
|
||||
''', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
await tester.createNewDocumentOnMobile('simple table');
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
// focus on the editor
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final firstParagraphPath = [0, 0, 0, 0];
|
||||
|
||||
// open the plus menu and select the table block
|
||||
{
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_table.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.type, equals(SimpleTableBlockKeys.type));
|
||||
expect(table.rowLength, equals(2));
|
||||
expect(table.columnLength, equals(2));
|
||||
|
||||
// focus on the first cell
|
||||
|
||||
final selection = editorState.selection!;
|
||||
expect(selection.isCollapsed, isTrue);
|
||||
expect(selection.start.path, equals(firstParagraphPath));
|
||||
}
|
||||
|
||||
// enable header column
|
||||
{
|
||||
// click the column menu button
|
||||
await tester.clickColumnMenuButton(0);
|
||||
|
||||
// enable header column
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_headerColumn
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// enable header row
|
||||
{
|
||||
// focus on the first cell
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: firstParagraphPath)),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the row menu button
|
||||
await tester.clickRowMenuButton(0);
|
||||
|
||||
// enable header column
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_headerRow.tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// check the table is updated
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.type, equals(SimpleTableBlockKeys.type));
|
||||
expect(table.isHeaderColumnEnabled, isTrue);
|
||||
expect(table.isHeaderRowEnabled, isTrue);
|
||||
|
||||
// set to page width
|
||||
{
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
final beforeWidth = table.width;
|
||||
// focus on the first cell
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: firstParagraphPath)),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the row menu button
|
||||
await tester.clickRowMenuButton(0);
|
||||
|
||||
// enable header column
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_setToPageWidth
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
// check the table is updated
|
||||
expect(table.width, greaterThan(beforeWidth));
|
||||
}
|
||||
|
||||
// distribute columns evenly
|
||||
{
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
final beforeWidth = table.width;
|
||||
|
||||
// focus on the first cell
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: firstParagraphPath)),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the column menu button
|
||||
await tester.clickColumnMenuButton(0);
|
||||
|
||||
// distribute columns evenly
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys
|
||||
.document_plugins_simpleTable_moreActions_distributeColumnsWidth
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
// check the table is updated
|
||||
expect(table.width, equals(beforeWidth));
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('''
|
||||
1. insert a simple table via + menu
|
||||
2. bold
|
||||
3. clear content
|
||||
''', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
await tester.createNewDocumentOnMobile('simple table');
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
// focus on the editor
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final firstParagraphPath = [0, 0, 0, 0];
|
||||
|
||||
// open the plus menu and select the table block
|
||||
{
|
||||
await tester.openPlusMenuAndClickButton(
|
||||
LocaleKeys.document_slashMenu_name_table.tr(),
|
||||
);
|
||||
|
||||
// check the block is inserted
|
||||
final table = editorState.getNodeAtPath([0])!;
|
||||
expect(table.type, equals(SimpleTableBlockKeys.type));
|
||||
expect(table.rowLength, equals(2));
|
||||
expect(table.columnLength, equals(2));
|
||||
|
||||
// focus on the first cell
|
||||
|
||||
final selection = editorState.selection!;
|
||||
expect(selection.isCollapsed, isTrue);
|
||||
expect(selection.start.path, equals(firstParagraphPath));
|
||||
}
|
||||
|
||||
await tester.ime.insertText('Hello');
|
||||
|
||||
// enable bold
|
||||
{
|
||||
// click the column menu button
|
||||
await tester.clickColumnMenuButton(0);
|
||||
|
||||
// enable bold
|
||||
await tester.clickSimpleTableBoldContentAction();
|
||||
await tester.cancelTableActionMenu();
|
||||
|
||||
// check the first cell is bold
|
||||
final paragraph = editorState.getNodeAtPath(firstParagraphPath)!;
|
||||
expect(paragraph.delta!, isNotEmpty);
|
||||
expect(
|
||||
paragraph.delta!.toJson(),
|
||||
equals([
|
||||
{
|
||||
'insert': 'Hello',
|
||||
'attributes': {'bold': true},
|
||||
}
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// clear content
|
||||
{
|
||||
// focus on the first cell
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(Position(path: firstParagraphPath)),
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the column menu button
|
||||
await tester.clickColumnMenuButton(0);
|
||||
|
||||
// clear content
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_clearContents
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
await tester.cancelTableActionMenu();
|
||||
|
||||
// check the first cell is empty
|
||||
final paragraph = editorState.getNodeAtPath(firstParagraphPath)!;
|
||||
expect(paragraph.delta!, isEmpty);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue