Compare commits

..
Author SHA1 Message Date
Kilu a7a40aebda feat: open editing 2024-10-28 14:39:40 +08:00
835 changed files with 13526 additions and 34611 deletions

No files matched your search

+108 -179
View File
@@ -1,197 +1,126 @@
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.3"
RUST_TOOLCHAIN: "1.80.1"
CARGO_MAKE_VERSION: "0.37.18"
CLOUD_VERSION: 0.6.54-amd64
# env:
# CARGO_TERM_COLOR: always
# FLUTTER_VERSION: "3.22.0"
# RUST_TOOLCHAIN: "1.77.2"
# CARGO_MAKE_VERSION: "0.36.6"
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: [ubuntu-latest]
runs-on: ${{ matrix.os }}
# jobs:
# build:
# if: github.event.pull_request.draft != true
# strategy:
# fail-fast: true
# matrix:
# os: [macos-14]
# 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
# # 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
- name: Check storage space
run: df -h
# - name: Check storage space
# run: df -h
- name: Checkout appflowy cloud code
uses: actions/checkout@v4
with:
repository: AppFlowy-IO/AppFlowy-Cloud
path: AppFlowy-Cloud
# - name: Checkout source code
# uses: actions/checkout@v4
- 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
# - uses: actions/setup-java@v4
# with:
# distribution: temurin
# java-version: 11
- 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 Rust toolchain
# id: rust_toolchain
# uses: actions-rs/toolchain@v1
# with:
# toolchain: ${{ env.RUST_TOOLCHAIN }}
# override: true
# profile: minimal
# 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
# - name: Install flutter
# id: flutter
# uses: subosito/flutter-action@v2
# with:
# channel: "stable"
# flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Checkout source code
uses: actions/checkout@v4
# - uses: gradle/gradle-build-action@v3
# with:
# gradle-version: 7.4.2
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
# - uses: davidB/rust-cargo-make@v1
# with:
# version: "0.36.6"
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
profile: minimal
# - 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 flutter
id: flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
# - name: Build AppFlowy
# working-directory: frontend
# run: |
# cargo make --profile development-android appflowy-android-dev-ci
- uses: gradle/gradle-build-action@v3
with:
gradle-version: 8.10
- 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 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
# - 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
+89 -28
View File
@@ -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.54-amd64
CLOUD_VERSION: 0.6.50
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,7 +74,7 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [windows-latest]
os: [ windows-latest ]
include:
- os: windows-latest
flutter_profile: development-windows-x86
@@ -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,20 +262,9 @@ 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. 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
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
docker rm -f $(docker ps -aq)
docker volume rm $(docker volume ls -q)
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
@@ -339,14 +328,14 @@ jobs:
flutter test integration_test/desktop/cloud/cloud_runner.dart -d Linux --coverage
shell: bash
integration_test:
needs: [prepare-linux]
# split the integration tests into different machines to minimize the time
integration_test_1:
needs: [ prepare-linux ]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
test_number: [1, 2, 3, 4, 5, 6, 7, 8, 9]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
@@ -355,10 +344,82 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Flutter Integration Test ${{ matrix.test_number }}
- name: Flutter Integration Test 1
uses: ./.github/actions/flutter_integration_test
with:
test_path: integration_test/desktop_runner_${{ matrix.test_number }}.dart
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
flutter_version: ${{ env.FLUTTER_VERSION }}
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
+18 -22
View File
@@ -20,8 +20,9 @@ on:
- "!frontend/appflowy_web_app/**"
env:
FLUTTER_VERSION: "3.22.3"
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.80.1"
CLOUD_VERSION: 0.6.50
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -96,26 +97,21 @@ jobs:
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
cargo make --profile development-ios-arm64-sim code_generation
- uses: futureware-tech/simulator-action@v3
id: simulator-action
with:
model: "iPhone 15"
shutdown_after_job: false
# - uses: futureware-tech/simulator-action@v3
# id: simulator-action
# with:
# model: "iPhone 15"
# shutdown_after_job: false
- name: Run AppFlowy on simulator
working-directory: frontend/appflowy_flutter
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
# 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 }}
# - name: Run integration tests
# working-directory: frontend/appflowy_flutter
# run: flutter test integration_test/runner.dart -d ${{ steps.simulator-action.outputs.udid }}
-83
View File
@@ -1,83 +0,0 @@
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 }}
+8 -17
View File
@@ -15,10 +15,12 @@ on:
- "main"
- "develop"
- "release/*"
paths:
- "frontend/rust-lib/**"
env:
CARGO_TERM_COLOR: always
CLOUD_VERSION: 0.7.6-amd64
CLOUD_VERSION: 0.6.50
RUST_TOOLCHAIN: "1.77.2"
jobs:
@@ -41,7 +43,6 @@ jobs:
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
sed -i '' 's|APPFLOWY_AI_OPENAI_API_KEY=.*|APPFLOWY_AI_OPENAI_API_KEY=${{ secrets.CI_OPENAI_API_KEY }}|' .env
- name: Ensure AppFlowy-Cloud is Running with Correct Version
working-directory: AppFlowy-Cloud
@@ -61,6 +62,8 @@ jobs:
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 rm -f $(docker ps -aq)
docker volume rm $(docker volume ls -q)
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
@@ -136,7 +139,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
@@ -146,20 +149,8 @@ jobs:
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
run: |
# 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 rm -f $(docker ps -aq)
docker volume rm $(docker volume ls -q)
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
-34
View File
@@ -1,38 +1,4 @@
# Release Notes
## 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
+17 -48
View File
@@ -42,12 +42,10 @@ 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)
@@ -63,18 +61,15 @@ 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**
@@ -82,24 +77,16 @@ 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 🌎🗺
[![translation badge](https://inlang.com/badge?url=github.com/AppFlowy-IO/AppFlowy)](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
@@ -109,30 +96,16 @@ run `npx inlang machine translate` to add missing translations.
## 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:
@@ -140,20 +113,16 @@ 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
View File
@@ -1,47 +0,0 @@
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 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
+1 -1
View File
@@ -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.6"
APPFLOWY_VERSION = "0.7.2"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
@@ -65,5 +65,4 @@
-->
<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>
@@ -6,7 +6,6 @@ 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';
@@ -15,31 +14,6 @@ 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,10 +1,33 @@
// 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();
@@ -1,47 +0,0 @@
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/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,220 +0,0 @@
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,16 +1,12 @@
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,13 +1,35 @@
// 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/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'package:universal_platform/universal_platform.dart';
import '../../../shared/constants.dart';
import '../../../shared/database_test_op.dart';
import '../../../shared/dir.dart';
import '../../../shared/emoji.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
void main() {
@@ -1,12 +1,21 @@
// 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,9 +1,24 @@
// 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,11 +1,33 @@
// 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,12 +1,21 @@
// 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';
@@ -40,10 +49,6 @@ 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,19 +1,38 @@
// 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 {
@@ -74,79 +93,5 @@ 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,17 +1,28 @@
// 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/patterns/common_patterns.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/constants.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
import '../../../shared/workspace.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -50,11 +61,11 @@ void main() {
final plainText = clipboardContent.plainText;
expect(
plainText,
matches(appflowySharePageLinkPattern),
startsWith(ShareConstants.shareBaseUrl),
);
final shareValues = plainText!
.replaceAll('https://${ShareConstants.shareBaseUrl}/', '')
.replaceAll('${ShareConstants.shareBaseUrl}/', '')
.split('/');
final workspaceId = shareValues[0];
expect(workspaceId, isNotEmpty);
@@ -1,75 +0,0 @@
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,11 +1,35 @@
// 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,23 +1,36 @@
// 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/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/plugins/shared/share/publish_tab.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/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/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() {
@@ -80,266 +93,4 @@ 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,7 +3,6 @@ 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;
@@ -15,5 +14,4 @@ void main() {
collaborative_workspace_test.main();
change_name_and_icon_test.main();
workspace_icon_test.main();
tabs_test.main();
}
@@ -211,7 +211,8 @@ void main() {
await tester.toggleIncludeTime();
// Select a date
DateTime now = DateTime.now();
final now = DateTime.now();
final expected = DateTime(now.year, now.month, now.day);
await tester.selectDay(content: now.day);
await tester.dismissCellEditor();
@@ -219,13 +220,13 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('MMM dd, y').format(now),
content: DateFormat('MMM dd, y').format(expected),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
// Toggle include time
now = DateTime.now();
// When toggling include time, the time value is from the previous existing date time, not the current time
await tester.toggleIncludeTime();
await tester.dismissCellEditor();
@@ -233,7 +234,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('MMM dd, y HH:mm').format(now),
content: DateFormat('MMM dd, y HH:mm').format(expected),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
@@ -248,7 +249,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('dd/MM/y HH:mm').format(now),
content: DateFormat('dd/MM/y HH:mm').format(expected),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
@@ -263,7 +264,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('dd/MM/y hh:mm a').format(now),
content: DateFormat('dd/MM/y hh:mm a').format(expected),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
@@ -292,6 +292,7 @@ void main() {
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
await tester.toggleIncludeTime();
final now = DateTime.now();
final expected = DateTime(now.year, now.month, now.day);
await tester.selectDay(content: now.day);
await tester.dismissCellEditor();
@@ -299,7 +300,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('MMM dd, y HH:mm').format(now),
content: DateFormat('MMM dd, y HH:mm').format(expected),
);
// open editor and change date & time format
@@ -313,7 +314,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('dd/MM/y hh:mm a').format(now),
content: DateFormat('dd/MM/y hh:mm a').format(expected),
);
});
@@ -540,6 +541,7 @@ void main() {
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
await tester.toggleIncludeTime();
final now = DateTime.now();
final expected = DateTime(now.year, now.month, now.day);
await tester.selectDay(content: now.day);
await tester.dismissCellEditor();
@@ -547,7 +549,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('MMM dd, y HH:mm').format(now),
content: DateFormat('MMM dd, y HH:mm').format(expected),
);
await tester.changeFieldTypeOfFieldWithName(
@@ -557,7 +559,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.RichText,
content: DateFormat('MMM dd, y HH:mm').format(now),
content: DateFormat('MMM dd, y HH:mm').format(expected),
cellIndex: 1,
);
@@ -581,7 +583,7 @@ void main() {
tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('MMM dd, y').format(now),
content: DateFormat('MMM dd, y').format(expected),
);
tester.assertCellContent(
rowIndex: 1,
@@ -68,7 +68,10 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapFileUploadHint();
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
// Expect one file
expect(find.byType(RenderMedia), findsOneWidget);
@@ -81,7 +84,9 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapFileUploadHint();
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
// Expect two files
@@ -133,7 +138,10 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapFileUploadHint();
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
// Expect two files
expect(find.byType(RenderMedia), findsNWidgets(2));
@@ -184,7 +192,10 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapFileUploadHint();
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
// Expect two files
expect(find.byType(RenderMedia), findsNWidgets(2));
@@ -218,7 +229,7 @@ void main() {
await Future.wait([firstFile.delete(), secondFile.delete()]);
});
testWidgets('show file names', (tester) async {
testWidgets('hide file names', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -260,7 +271,10 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapFileUploadHint();
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
// Expect two files
expect(find.byType(RenderMedia), findsNWidgets(2));
@@ -268,28 +282,28 @@ void main() {
await tester.dismissCellEditor();
await tester.pumpAndSettle();
// Open first row in row detail view then toggle show file names
// Open first row in row detail view then toggle hide 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,10 +4,12 @@ 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';
@@ -24,6 +26,63 @@ 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/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: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/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,24 +76,6 @@ 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 {
@@ -1,20 +0,0 @@
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.
}
@@ -1,22 +0,0 @@
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.
}
@@ -1,47 +0,0 @@
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);
});
});
}
@@ -1,144 +0,0 @@
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,
);
},
);
}
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
@@ -50,6 +51,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
// Delete from editor
await tester.editor.updateSelection(
@@ -123,6 +125,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
// Cut from editor
await tester.editor.updateSelection(
@@ -148,6 +151,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
// Cut again
await tester.editor.updateSelection(
@@ -191,6 +195,7 @@ void main() {
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
});
testWidgets(
'Cut+paste in same docuemnt and then paste again in same document',
@@ -209,6 +214,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
// Cut from editor
await tester.editor.updateSelection(
@@ -234,6 +240,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
// Paste again
await tester.simulateKeyEvent(
@@ -245,6 +252,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsNWidgets(2));
expect(find.text('$_createdPageName (copy)'), findsNWidgets(2));
});
@@ -263,6 +271,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
final headingText = LocaleKeys.document_slashMenu_name_heading1.tr();
final paragraphText = LocaleKeys.document_slashMenu_name_text.tr();
@@ -274,6 +283,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
// Turn into paragraph
await tester.editor.openTurnIntoMenu([0]);
@@ -282,6 +292,7 @@ void main() {
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsOneWidget);
});
testWidgets('Duplicate a block containing two sub page mentions',
@@ -320,6 +331,7 @@ void main() {
expect(find.text(_createdPageName), findsOneWidget);
expect(find.text("$_createdPageName (copy)"), findsOneWidget);
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
expect(find.byFlowySvg(FlowySvgs.child_page_s), findsNWidgets(2));
// Duplicate node from block action menu
await tester.editor.hoverAndClickOptionMenuButton([0]);
@@ -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,7 +73,10 @@ void main() {
);
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionMenuButton([0]);
@@ -96,7 +99,10 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionAddButton([0], false);
@@ -149,7 +155,10 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionAddButton([0], false);
@@ -207,7 +216,10 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor
@@ -248,7 +260,10 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor
@@ -298,7 +313,10 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionMenuButton([0]);
@@ -308,11 +326,6 @@ 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,
@@ -341,7 +354,10 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
// Delete
@@ -389,16 +405,15 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
expect(find.byType(SubPageBlockComponent), findsOneWidget);
await tester.hoverOnPageName(
'Child page',
onHover: () async {
await tester.tapDeletePageButton();
},
);
await tester.hoverOnPageName('Child page');
await tester.tapDeletePageButton();
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Child page'), findsNothing);
@@ -417,7 +432,10 @@ void main() {
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionMenuButton([0]);
@@ -455,49 +473,6 @@ 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));
});
});
}
@@ -523,16 +498,4 @@ 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,12 +1,26 @@
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_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() {
@@ -19,5 +33,17 @@ void main() {
document_with_inline_page_test.main();
document_with_inline_math_equation_test.main();
document_with_cover_image_test.main();
// Don't add new tests here.
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_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
}
@@ -9,18 +9,18 @@ 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();
}
@@ -1,22 +0,0 @@
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.
}
@@ -1,31 +0,0 @@
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,27 +347,5 @@ 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);
});
});
}
@@ -1,50 +0,0 @@
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,
);
});
});
}
@@ -2,7 +2,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/mention/mention_date_block.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:easy_localization/easy_localization.dart';
@@ -33,6 +32,7 @@ void main() {
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
offset: 100,
);
final dateTimeSettings = DateTimeSettingsPB(
@@ -56,13 +56,11 @@ void main() {
await tester.pumpAndSettle();
// add time 11:12
final textField = find
.descendant(
of: find.byType(DesktopAppFlowyDatePicker),
matching: find.byType(TextField),
)
.last;
await tester.pumpUntilFound(textField);
final currentTime = DateFormat('HH:mm').format(DateTime.now());
final textField = find.byWidgetPredicate(
(widget) =>
widget is TextField && widget.controller!.text == currentTime,
);
await tester.enterText(textField, "11:12");
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
@@ -86,6 +84,7 @@ void main() {
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
offset: 100,
);
final dateTimeSettings = DateTimeSettingsPB(
@@ -47,7 +47,9 @@ void main() {
mockPickFilePaths(paths: [filePath]);
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
await tester.tapFileUploadHint();
await tester.tap(
find.text(LocaleKeys.document_plugins_file_fileUploadHint.tr()),
);
await tester.pumpAndSettle();
expect(find.byType(FileUploadMenu), findsNothing);
@@ -24,70 +24,11 @@ import 'package:run_with_network_images/run_with_network_images.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
const _testImageUrls = [
'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',
'https://www.easygifanimator.net/images/samples/eglite.gif',
'https://people.math.sc.edu/Burkardt/data/bmp/snail.bmp',
'https://file-examples.com/storage/fe9566cb7d67345489a5a97/2017/10/file_example_JPG_100kB.jpg',
];
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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();
@@ -138,11 +79,59 @@ void main() {
file.deleteSync();
});
for (final url in _testImageUrls) {
testWidgets('insert an image from network: $url', (tester) async {
await testEmbedImage(tester, url);
});
}
testWidgets('insert an image from network', (tester) 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(),
);
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 {
await runWithNetworkImages(() async {
@@ -6,6 +6,7 @@ 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';
@@ -48,7 +49,7 @@ void main() {
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
offset: 80,
offset: 100,
);
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
@@ -146,7 +147,7 @@ void main() {
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
offset: 80,
offset: 100,
);
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
@@ -1,7 +1,6 @@
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';
@@ -44,9 +43,6 @@ void main() {
* # Heading 1
* ## Heading 2
* ### Heading 3
* > # Heading 1
* > ## Heading 2
* > ### Heading 3
*/
await tester.editor.tapLineOfEditorAt(3);
@@ -57,7 +53,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading1),
),
findsNWidgets(2),
findsOneWidget,
);
// Heading 2 is prefixed with a bullet
@@ -66,7 +62,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading2),
),
findsNWidgets(2),
findsOneWidget,
);
// Heading 3 is prefixed with a dash
@@ -75,7 +71,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading3),
),
findsNWidgets(2),
findsOneWidget,
);
// update the Heading 1 to Heading 1Hello world
@@ -103,16 +99,13 @@ void main() {
* # Heading 1
* ## Heading 2
* ### Heading 3
* > # Heading 1
* > ## Heading 2
* > ### Heading 3
*/
await tester.editor.tapLineOfEditorAt(7);
await tester.editor.tapLineOfEditorAt(3);
await insertOutlineInDocument(tester);
// expect to find only the `heading1` widget under the [OutlineBlockWidget]
await hoverAndClickDepthOptionAction(tester, [6], 1);
await hoverAndClickDepthOptionAction(tester, [3], 1);
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
@@ -130,7 +123,7 @@ void main() {
//////
/// expect to find only the 'heading1' and 'heading2' under the [OutlineBlockWidget]
await hoverAndClickDepthOptionAction(tester, [6], 2);
await hoverAndClickDepthOptionAction(tester, [3], 2);
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
@@ -141,13 +134,13 @@ void main() {
//////
// expect to find all the headings under the [OutlineBlockWidget]
await hoverAndClickDepthOptionAction(tester, [6], 3);
await hoverAndClickDepthOptionAction(tester, [3], 3);
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
matching: find.text(heading1),
),
findsNWidgets(2),
findsOneWidget,
);
expect(
@@ -155,7 +148,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading2),
),
findsNWidgets(2),
findsOneWidget,
);
expect(
@@ -163,7 +156,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading3),
),
findsNWidgets(2),
findsOneWidget,
);
//////
});
@@ -176,6 +169,7 @@ 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();
}
@@ -193,17 +187,7 @@ 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);
}
@@ -1,370 +0,0 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.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();
// hover on the table
final tableBlock = find.byType(SimpleTableBlockWidget).first;
await tester.hoverOnWidget(
tableBlock,
onHover: () async {
// click the add row button
final addRowButton = find.byType(SimpleTableAddRowButton).first;
await tester.tap(addRowButton);
// click the add column button
final addColumnButton = find.byType(SimpleTableAddColumnButton).first;
await tester.tap(addColumnButton);
// 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);
});
});
}
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);
}
}
@@ -1,8 +1,6 @@
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';
@@ -62,50 +60,6 @@ void main() {
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');
});
});
}
@@ -122,7 +76,7 @@ Future<void> _insertToggleHeadingBlockInDocument(
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
name,
offset: 150,
offset: 100,
);
await tester.pumpAndSettle();
}
@@ -1,9 +1,7 @@
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';
@@ -265,24 +263,5 @@ void main() {
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);
});
});
}
@@ -1,19 +0,0 @@
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.
}
@@ -19,13 +19,8 @@ void main() {
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();
},
);
await tester.tap(find.byType(ViewItem), buttons: kSecondaryButton);
await tester.pumpAndSettle();
// Change icon
final changeIconButton =
@@ -11,7 +11,7 @@ void main() {
testWidgets('empty test', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.wait(500);
await tester.wait(1000);
});
});
}
@@ -1,6 +1,5 @@
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';
@@ -88,7 +87,7 @@ void main() {
);
expect(
importedPageEditorState.getNodeAtPath([2])!.type,
SimpleTableBlockKeys.type,
TableBlockKeys.type,
);
});
});
@@ -1,19 +1,17 @@
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';
@@ -22,12 +20,17 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Tabs', () {
testWidgets('open/navigate/close tabs', (tester) async {
testWidgets('Open AppFlowy and open/navigate/close tabs', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// No tabs rendered yet
expect(find.byType(FlowyTab), findsNothing);
expect(
find.descendant(
of: find.byType(TabsManager),
matching: find.byType(TabBar),
),
findsNothing,
);
await tester.createNewPageWithNameUnderParent(name: _documentName);
@@ -41,7 +44,7 @@ void main() {
expect(
find.descendant(
of: find.byType(TabsManager),
of: find.byType(TabBar),
matching: find.byType(FlowyTab),
),
findsNWidgets(3),
@@ -68,267 +71,11 @@ void main() {
expect(
find.descendant(
of: find.byType(TabsManager),
of: find.byType(TabBar),
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();
}
}
@@ -1,21 +0,0 @@
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,6 +1,7 @@
import 'package:integration_test/integration_test.dart';
import 'desktop/document/document_test_runner_1.dart' as document_test_runner_1;
import 'desktop/document/document_test_runner_1.dart' as document_test_runner;
import 'desktop/uncategorized/empty_test.dart' as first_test;
import 'desktop/uncategorized/switch_folder_test.dart' as switch_folder_test;
Future<void> main() async {
@@ -10,7 +11,11 @@ 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_1.main();
document_test_runner.main();
// DON'T add more tests here.
}
@@ -1,7 +1,18 @@
import 'package:integration_test/integration_test.dart';
import 'desktop/database/database_test_runner_1.dart' as database_test_runner_1;
import 'desktop/first_test/first_test.dart' as first_test;
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;
Future<void> main() async {
await runIntegration2OnDesktop();
@@ -10,8 +21,20 @@ 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_test_runner_1.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();
// DON'T add more tests here. This is the second test runner for desktop.
}
@@ -1,8 +1,24 @@
import 'package:integration_test/integration_test.dart';
import 'desktop/board/board_test_runner.dart' as board_test_runner;
import 'desktop/first_test/first_test.dart' as first_test;
import 'desktop/grid/grid_test_runner_1.dart' as grid_test_runner_1;
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;
Future<void> main() async {
await runIntegration3OnDesktop();
@@ -11,9 +27,26 @@ 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();
grid_test_runner_1.main();
// DON'T add more tests here.
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
}
@@ -1,7 +1,7 @@
import 'package:integration_test/integration_test.dart';
import 'desktop/document/document_test_runner_2.dart' as document_test_runner_2;
import 'desktop/first_test/first_test.dart' as first_test;
import 'desktop/uncategorized/empty_test.dart' as first_test;
Future<void> main() async {
await runIntegration4OnDesktop();
@@ -10,8 +10,8 @@ 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();
// DON'T add more tests here.
}
@@ -1,17 +0,0 @@
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.
}
@@ -1,22 +0,0 @@
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.
}
@@ -1,17 +0,0 @@
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.
}
@@ -1,17 +0,0 @@
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.
}
@@ -1,16 +0,0 @@
import 'package:integration_test/integration_test.dart';
import 'desktop/uncategorized/tabs_test.dart' as tabs_test;
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();
}
@@ -1,11 +1,9 @@
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();
}
@@ -1,23 +1,47 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'dart:math';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/home/workspaces/create_workspace_menu.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
import 'package:appflowy/mobile/presentation/home/home.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/constants.dart';
import '../../../shared/dir.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('publish:', () {
testWidgets('''
1. publish document
2. update path name
3. unpublish document
''', (tester) async {
testWidgets('publish document', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
@@ -51,51 +75,6 @@ void main() {
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(),
@@ -1,12 +1,39 @@
// 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() {
@@ -1,287 +0,0 @@
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,11 +1,37 @@
// 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,7 +1,6 @@
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 'title_test.dart' as title_test;
void main() {
@@ -10,5 +9,4 @@ void main() {
// Document integration tests
title_test.main();
page_style_test.main();
plus_menu_test.main();
}
@@ -1,19 +1,42 @@
// 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)
@@ -34,9 +57,11 @@ void main() {
.single
.widget as AppFlowyEditorPage;
return editorPage.styleCustomizer
.style()
.textStyleConfiguration
.lineHeight;
.style()
.textStyleConfiguration
.text
.height ??
PageStyleLineHeightLayout.normal.lineHeight;
}
testWidgets('change font size in page style settings', (tester) async {
@@ -62,24 +87,20 @@ void main() {
await tester.openPage(gettingStarted);
// click the layout button
await tester.tapButton(find.byType(MobileViewPageLayoutButton));
var lineHeight = getCurrentEditorLineHeight();
expect(
lineHeight,
getCurrentEditorLineHeight(),
PageStyleLineHeightLayout.normal.lineHeight,
);
// change line height from normal to large
await tester.tapSvgButton(FlowySvgs.m_layout_large_s);
await tester.pumpAndSettle();
lineHeight = getCurrentEditorLineHeight();
expect(
lineHeight,
getCurrentEditorLineHeight(),
PageStyleLineHeightLayout.large.lineHeight,
);
// change line height from large to small
await tester.tapSvgButton(FlowySvgs.m_layout_small_s);
lineHeight = getCurrentEditorLineHeight();
expect(
lineHeight,
getCurrentEditorLineHeight(),
PageStyleLineHeightLayout.small.lineHeight,
);
});
@@ -1,89 +0,0 @@
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]))),
);
});
});
}
@@ -1,10 +1,39 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
import 'package:appflowy/mobile/presentation/home/home.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../shared/dir.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
void main() {
@@ -1,25 +1,47 @@
// 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/presentation/home/home.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/plugins/document/document_page.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
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/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() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('create new page in home page:', () {
group('create new page', () {
testWidgets('create document', (tester) async {
await tester.launchInAnonymousMode();
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.local,
);
// tap the create page button
final createPageButton = find.byWidgetPredicate(
(widget) =>
widget is FlowySvg &&
widget.svg.path == FlowySvgs.m_home_add_m.path,
widget.svg.path == FlowySvgs.m_home_unselected_m.path,
);
await tester.tapButton(createPageButton);
await tester.pumpAndSettle();
expect(find.byType(MobileDocumentScreen), findsOneWidget);
});
});
@@ -1,15 +1,33 @@
// 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/mobile/presentation/home/home.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/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() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('anonymous sign in on mobile:', () {
group('anonymous sign in on mobile', () {
testWidgets('anon user and then sign in', (tester) async {
await tester.launchInAnonymousMode();
await tester.initializeAppFlowy();
// expect to see the home page
expect(find.byType(MobileHomeScreen), findsOneWidget);
@@ -1,20 +1,15 @@
import 'package:appflowy_backend/log.dart';
import 'package:integration_test/integration_test.dart';
import 'mobile/document/document_test_runner.dart' as document_test_runner;
import 'mobile/document/page_style_test.dart' as page_style_test;
import 'mobile/home_page/create_new_page_test.dart' as create_new_page_test;
import 'mobile/sign_in/anonymous_sign_in_test.dart' as anonymous_sign_in_test;
Future<void> main() async {
Future<void> runIntegrationOnMobile() async {
Log.shared.disableLog = true;
await runIntegration1OnMobile();
}
Future<void> runIntegration1OnMobile() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
anonymous_sign_in_test.main();
create_new_page_test.main();
document_test_runner.main();
page_style_test.main();
}
@@ -4,12 +4,7 @@ import 'desktop_runner_1.dart';
import 'desktop_runner_2.dart';
import 'desktop_runner_3.dart';
import 'desktop_runner_4.dart';
import 'desktop_runner_5.dart';
import 'desktop_runner_6.dart';
import 'desktop_runner_7.dart';
import 'desktop_runner_8.dart';
import 'desktop_runner_9.dart';
import 'mobile_runner_1.dart';
import 'mobile_runner.dart';
/// The main task runner for all integration tests in AppFlowy.
///
@@ -24,13 +19,8 @@ Future<void> main() async {
await runIntegration2OnDesktop();
await runIntegration3OnDesktop();
await runIntegration4OnDesktop();
await runIntegration5OnDesktop();
await runIntegration6OnDesktop();
await runIntegration7OnDesktop();
await runIntegration8OnDesktop();
await runIntegration9OnDesktop();
} else if (Platform.isIOS || Platform.isAndroid) {
await runIntegration1OnMobile();
await runIntegrationOnMobile();
} else {
throw Exception('Unsupported platform');
}
@@ -4,10 +4,8 @@ import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart';
import 'package:appflowy/plugins/shared/share/share_button.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
@@ -44,7 +42,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:universal_platform/universal_platform.dart';
import 'emoji.dart';
import 'util.dart';
@@ -195,13 +192,8 @@ extension CommonOperations on WidgetTester {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
final page = findPageName(name, layout: layout);
await hoverOnPageName(
name,
onHover: () async {
await tap(page, buttons: kSecondaryMouseButton);
await pumpAndSettle();
},
);
await tap(page, buttons: kSecondaryMouseButton);
await pumpAndSettle();
}
/// open the page with given name.
@@ -319,15 +311,6 @@ extension CommonOperations on WidgetTester {
await tapButton(shareButton);
}
// open the share menu and then click the publish tab
Future<void> openPublishMenu() async {
await tapShareButton();
final publishButton = find.textContaining(
LocaleKeys.shareAction_publishTab.tr(),
);
await tapButton(publishButton);
}
/// Tap the export markdown button
///
/// Must call [tapShareButton] first.
@@ -778,69 +761,6 @@ extension CommonOperations on WidgetTester {
await tap(button);
await pump();
}
Future<void> tapFileUploadHint() async {
final finder = find.byWidgetPredicate(
(w) =>
w is RichText &&
w.text.toPlainText().contains(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
),
);
await tap(finder);
await pumpAndSettle(const Duration(seconds: 2));
}
/// Create a new document on mobile
Future<void> createNewDocumentOnMobile(String name) async {
final createPageButton = find.byKey(
BottomNavigationBarItemType.add.valueKey,
);
await tapButton(createPageButton);
expect(find.byType(MobileDocumentScreen), findsOneWidget);
final title = editor.findDocumentTitle('');
expect(title, findsOneWidget);
final textField = widget<TextField>(title);
expect(textField.focusNode!.hasFocus, isTrue);
// input new name and press done button
await enterText(title, name);
await testTextInput.receiveAction(TextInputAction.done);
await pumpAndSettle();
final newTitle = editor.findDocumentTitle(name);
expect(newTitle, findsOneWidget);
expect(textField.controller!.text, name);
}
/// Open the plus menu
Future<void> openPlusMenuAndClickButton(String buttonName) async {
assert(
UniversalPlatform.isMobile,
'This method is only supported on mobile platforms',
);
final plusMenuButton = find.byKey(addBlockToolbarItemKey);
final addMenuItem = find.byType(AddBlockMenu);
await tapButton(plusMenuButton);
await pumpUntilFound(addMenuItem);
final toggleHeading1 = find.byWidgetPredicate(
(widget) =>
widget is TypeOptionMenuItem && widget.value.text == buttonName,
);
final scrollable = find.ancestor(
of: find.byType(TypeOptionGridView),
matching: find.byType(Scrollable),
);
await scrollUntilVisible(
toggleHeading1,
100,
scrollable: scrollable,
);
await tapButton(toggleHeading1);
await pumpUntilNotFound(addMenuItem);
}
}
extension SettingsFinder on CommonFinders {
@@ -596,10 +596,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
final banner = find.byType(RowBanner);
expect(banner, findsOneWidget);
await startGesture(
getCenter(banner) + const Offset(0, -10),
kind: PointerDeviceKind.mouse,
);
await startGesture(getCenter(banner), kind: PointerDeviceKind.mouse);
await pumpAndSettle();
}
@@ -611,6 +608,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapButtonWithName(
LocaleKeys.document_plugins_cover_addCover.tr(),
);
await pumpAndSettle();
}
Future<void> openEmojiPicker() async =>
@@ -4,13 +4,11 @@ import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/account_user_profile.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'base.dart';
@@ -118,20 +116,4 @@ extension AppFlowySettings on WidgetTester {
await tapAt(Offset.zero);
await pumpAndSettle();
}
Future<void> updateNamespace(String namespace) async {
final dialog = find.byType(DomainSettingsDialog);
expect(dialog, findsOneWidget);
// input the new namespace
await enterText(
find.descendant(
of: dialog,
matching: find.byType(TextField),
),
namespace,
);
await tapButton(find.text(LocaleKeys.button_save.tr()));
await pumpAndSettle();
}
}
@@ -40,13 +40,9 @@ extension AppFlowyWorkspace on WidgetTester {
moreButton,
onHover: () async {
await tapButton(moreButton);
// wait for the menu to open
final renameButton = find.findTextInFlowyText(
LocaleKeys.button_rename.tr(),
await tapButton(
find.findTextInFlowyText(LocaleKeys.button_rename.tr()),
);
await pumpUntilFound(renameButton);
expect(renameButton, findsOneWidget);
await tapButton(renameButton);
final input = find.byType(TextFormField);
expect(input, findsOneWidget);
await enterText(input, name);
@@ -50,8 +50,6 @@
<string>AppFlowy needs access to your photos to let you add images to your documents</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>AppFlowy needs access to your camera to let you add images to your documents from camera</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@@ -67,9 +65,9 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UISupportsDocumentBrowser</key>
<true/>
</dict>
</plist>
@@ -49,7 +49,6 @@ class KVKeys {
/// {'SidebarFolderCategoryType.value': true}
static const String expandedFolders = 'expandedFolders';
/// @deprecated in version 0.7.6
/// The key for saving if showing the rename dialog when creating a new file
///
/// The value is a boolean string.
@@ -20,7 +20,6 @@ extension MobileRouter on BuildContext {
bool addInRecent = true,
bool showMoreButton = true,
String? fixedTitle,
String? blockId,
}) async {
// set the current view before pushing the new view
getIt<MenuSharedState>().latestOpenView = view;
@@ -33,9 +32,6 @@ extension MobileRouter on BuildContext {
if (fixedTitle != null) {
queryParameters[MobileDocumentScreen.viewFixedTitle] = fixedTitle;
}
if (blockId != null) {
queryParameters[MobileDocumentScreen.viewBlockId] = blockId;
}
}
final uri = Uri(
@@ -61,7 +61,6 @@ class NotificationReminderBloc
reminderContent: node.delta?.toPlainText() ?? '',
nodes: [node],
status: NotificationReminderStatus.loaded,
blockId: reminder.meta[ReminderMetaKeys.blockId],
),
);
}
@@ -206,7 +205,6 @@ class NotificationReminderState with _$NotificationReminderState {
@Default(NotificationReminderStatus.initial)
NotificationReminderStatus status,
@Default([]) List<Node> nodes,
String? blockId,
ViewPB? view,
}) = _NotificationReminderState;
@@ -30,7 +30,6 @@ class MobileViewPage extends StatefulWidget {
this.arguments,
this.fixedTitle,
this.showMoreButton = true,
this.blockId,
});
/// view id
@@ -39,7 +38,6 @@ class MobileViewPage extends StatefulWidget {
final String? title;
final Map<String, dynamic>? arguments;
final bool showMoreButton;
final String? blockId;
// only used in row page
final String? fixedTitle;
@@ -65,10 +63,6 @@ class _MobileViewPageState extends State<MobileViewPage> {
@override
void dispose() {
_appBarOpacity.dispose();
// there's no need to remove the listener, because the observer will be disposed when the widget is unmounted.
// inside the observer, the listener will be removed automatically.
// _scrollNotificationObserver?.removeListener(_onScrollNotification);
_scrollNotificationObserver = null;
super.dispose();
@@ -183,7 +177,6 @@ class _MobileViewPageState extends State<MobileViewPage> {
context: PluginContext(userProfile: state.userProfilePB),
data: {
MobileDocumentScreen.viewFixedTitle: widget.fixedTitle,
MobileDocumentScreen.viewBlockId: widget.blockId,
},
);
},
@@ -9,14 +9,12 @@ class TypeOptionMenuItemValue<T> {
required this.text,
required this.backgroundColor,
required this.onTap,
this.iconPadding,
});
final T value;
final FlowySvgData icon;
final String text;
final Color backgroundColor;
final EdgeInsets? iconPadding;
final void Function(BuildContext context, T value) onTap;
}
@@ -24,7 +22,7 @@ class TypeOptionMenu<T> extends StatelessWidget {
const TypeOptionMenu({
super.key,
required this.values,
this.width = 98,
this.width = 94,
this.iconWidth = 72,
this.scaleFactor = 1.0,
this.maxAxisSpacing = 18,
@@ -41,18 +39,17 @@ class TypeOptionMenu<T> extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TypeOptionGridView(
return _GridView(
crossAxisCount: crossAxisCount,
mainAxisSpacing: maxAxisSpacing * scaleFactor,
itemWidth: width * scaleFactor,
children: values
.map(
(value) => TypeOptionMenuItem<T>(
(value) => _TypeOptionMenuItem<T>(
value: value,
width: width,
iconWidth: iconWidth,
scaleFactor: scaleFactor,
iconPadding: value.iconPadding,
),
)
.toList(),
@@ -60,21 +57,18 @@ class TypeOptionMenu<T> extends StatelessWidget {
}
}
class TypeOptionMenuItem<T> extends StatelessWidget {
const TypeOptionMenuItem({
super.key,
class _TypeOptionMenuItem<T> extends StatelessWidget {
const _TypeOptionMenuItem({
required this.value,
this.width = 94,
this.iconWidth = 72,
this.scaleFactor = 1.0,
this.iconPadding,
});
final TypeOptionMenuItemValue<T> value;
final double iconWidth;
final double width;
final double scaleFactor;
final EdgeInsets? iconPadding;
double get scaledIconWidth => iconWidth * scaleFactor;
double get scaledWidth => width * scaleFactor;
@@ -94,8 +88,7 @@ class TypeOptionMenuItem<T> extends StatelessWidget {
borderRadius: BorderRadius.circular(24 * scaleFactor),
),
),
padding: EdgeInsets.all(21 * scaleFactor) +
(iconPadding ?? EdgeInsets.zero),
padding: EdgeInsets.all(21 * scaleFactor),
child: FlowySvg(
value.icon,
),
@@ -120,9 +113,8 @@ class TypeOptionMenuItem<T> extends StatelessWidget {
}
}
class TypeOptionGridView extends StatelessWidget {
const TypeOptionGridView({
super.key,
class _GridView extends StatelessWidget {
const _GridView({
required this.children,
required this.crossAxisCount,
required this.mainAxisSpacing,
@@ -3,24 +3,17 @@ import 'dart:async';
import 'package:appflowy/core/helpers/url_launcher.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/workspaces/create_workspace_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_notification.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/publish_name_generator.dart';
import 'package:appflowy/plugins/shared/share/share_bloc.dart';
import 'package:appflowy/shared/error_code/error_code_map.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -30,97 +23,83 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
const MobileViewPageMoreBottomSheet({super.key, required this.view});
final ViewPB view;
@override
Widget build(BuildContext context) {
return BlocListener<ShareBloc, ShareState>(
listener: (context, state) => _showToast(context, state),
child: BlocListener<ViewBloc, ViewState>(
listener: (context, state) {
if (state.successOrFailure.isSuccess && state.isDeleted) {
context.go('/home');
return BlocListener<ViewBloc, ViewState>(
listener: (context, state) {
if (state.successOrFailure.isSuccess && state.isDeleted) {
context.go('/home');
}
},
child: ViewPageBottomSheet(
view: view,
onAction: (action) async {
switch (action) {
case MobileViewBottomSheetBodyAction.duplicate:
context.read<ViewBloc>().add(const ViewEvent.duplicate());
context.pop();
break;
case MobileViewBottomSheetBodyAction.delete:
context.read<ViewBloc>().add(const ViewEvent.delete());
context.pop();
break;
case MobileViewBottomSheetBodyAction.addToFavorites:
case MobileViewBottomSheetBodyAction.removeFromFavorites:
context.read<FavoriteBloc>().add(FavoriteEvent.toggle(view));
context.pop();
break;
case MobileViewBottomSheetBodyAction.undo:
EditorNotification.undo().post();
context.pop();
break;
case MobileViewBottomSheetBodyAction.redo:
EditorNotification.redo().post();
context.pop();
break;
case MobileViewBottomSheetBodyAction.helpCenter:
// unimplemented
context.pop();
break;
case MobileViewBottomSheetBodyAction.publish:
await _publish(context);
if (context.mounted) {
context.pop();
}
break;
case MobileViewBottomSheetBodyAction.unpublish:
_unpublish(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.copyPublishLink:
_copyPublishLink(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.visitSite:
_visitPublishedSite(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.copyShareLink:
_copyShareLink(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.rename:
// no need to implement, rename is handled by the onRename callback.
throw UnimplementedError();
}
},
child: ViewPageBottomSheet(
view: view,
onAction: (action) async => _onAction(context, action),
onRename: (name) {
_onRename(context, name);
context.pop();
},
),
onRename: (name) {
_onRename(context, name);
context.pop();
},
),
);
}
Future<void> _onAction(
BuildContext context,
MobileViewBottomSheetBodyAction action,
) async {
switch (action) {
case MobileViewBottomSheetBodyAction.duplicate:
_duplicate(context);
break;
case MobileViewBottomSheetBodyAction.delete:
context.read<ViewBloc>().add(const ViewEvent.delete());
context.pop();
break;
case MobileViewBottomSheetBodyAction.addToFavorites:
_addFavorite(context);
break;
case MobileViewBottomSheetBodyAction.removeFromFavorites:
_removeFavorite(context);
break;
case MobileViewBottomSheetBodyAction.undo:
EditorNotification.undo().post();
context.pop();
break;
case MobileViewBottomSheetBodyAction.redo:
EditorNotification.redo().post();
context.pop();
break;
case MobileViewBottomSheetBodyAction.helpCenter:
// unimplemented
context.pop();
break;
case MobileViewBottomSheetBodyAction.publish:
await _publish(context);
if (context.mounted) {
context.pop();
}
break;
case MobileViewBottomSheetBodyAction.unpublish:
_unpublish(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.copyPublishLink:
_copyPublishLink(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.visitSite:
_visitPublishedSite(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.copyShareLink:
_copyShareLink(context);
context.pop();
break;
case MobileViewBottomSheetBodyAction.updatePathName:
_updatePathName(context);
case MobileViewBottomSheetBodyAction.rename:
// no need to implement, rename is handled by the onRename callback.
throw UnimplementedError();
}
}
Future<void> _publish(BuildContext context) async {
final id = context.read<ShareBloc>().view.id;
final lastPublishName = context.read<ShareBloc>().state.pathName;
final publishName = lastPublishName.orDefault(
await generatePublishName(
id,
view.name,
),
final publishName = await generatePublishName(
id,
view.name,
);
if (context.mounted) {
context.read<ShareBloc>().add(
@@ -130,44 +109,19 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
[view.id],
),
);
showToastNotification(
context,
message: LocaleKeys.publish_publishSuccessfully.tr(),
);
}
}
void _duplicate(BuildContext context) {
context.read<ViewBloc>().add(const ViewEvent.duplicate());
context.pop();
showToastNotification(
context,
message: LocaleKeys.button_duplicateSuccessfully.tr(),
);
}
void _addFavorite(BuildContext context) {
_toggleFavorite(context);
showToastNotification(
context,
message: LocaleKeys.button_favoriteSuccessfully.tr(),
);
}
void _removeFavorite(BuildContext context) {
_toggleFavorite(context);
showToastNotification(
context,
message: LocaleKeys.button_unfavoriteSuccessfully.tr(),
);
}
void _toggleFavorite(BuildContext context) {
context.read<FavoriteBloc>().add(FavoriteEvent.toggle(view));
context.pop();
}
void _unpublish(BuildContext context) {
context.read<ShareBloc>().add(const ShareEvent.unPublish());
showToastNotification(
context,
message: LocaleKeys.publish_unpublishSuccessfully.tr(),
);
}
void _copyPublishLink(BuildContext context) {
@@ -228,112 +182,4 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
context.read<ViewBloc>().add(ViewEvent.rename(name));
}
}
void _updatePathName(BuildContext context) async {
final shareBloc = context.read<ShareBloc>();
final pathName = shareBloc.state.pathName;
await showMobileBottomSheet(
context,
showHeader: true,
title: LocaleKeys.shareAction_updatePathName.tr(),
showCloseButton: true,
showDragHandle: true,
showDivider: false,
padding: const EdgeInsets.symmetric(horizontal: 16),
builder: (bottomSheetContext) {
FlowyResult<void, FlowyError>? previousUpdatePathNameResult;
return EditWorkspaceNameBottomSheet(
type: EditWorkspaceNameType.edit,
workspaceName: pathName,
hintText: '',
validator: (value) => null,
validatorBuilder: (context) {
return BlocProvider.value(
value: shareBloc,
child: BlocBuilder<ShareBloc, ShareState>(
builder: (context, state) {
final updatePathNameResult = state.updatePathNameResult;
if (updatePathNameResult == null &&
previousUpdatePathNameResult == null) {
return const SizedBox.shrink();
}
if (updatePathNameResult != null) {
previousUpdatePathNameResult = updatePathNameResult;
}
final widget = previousUpdatePathNameResult?.fold(
(value) => const SizedBox.shrink(),
(error) => FlowyText(
error.code.publishErrorMessage.orDefault(
LocaleKeys.settings_sites_error_updatePathNameFailed
.tr(),
),
maxLines: 3,
fontSize: 12,
textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis,
color: Theme.of(context).colorScheme.error,
),
) ??
const SizedBox.shrink();
return widget;
},
),
);
},
onSubmitted: (name) {
// rename the path name
Log.info('rename the path name, from: $pathName, to: $name');
shareBloc.add(ShareEvent.updatePathName(name));
},
);
},
);
shareBloc.add(const ShareEvent.clearPathNameResult());
}
void _showToast(BuildContext context, ShareState state) {
if (state.publishResult != null) {
state.publishResult!.fold(
(value) => showToastNotification(
context,
message: LocaleKeys.publish_publishSuccessfully.tr(),
),
(error) => showToastNotification(
context,
message: '${LocaleKeys.publish_publishFailed.tr()}: ${error.code}',
type: ToastificationType.error,
),
);
} else if (state.unpublishResult != null) {
state.unpublishResult!.fold(
(value) => showToastNotification(
context,
message: LocaleKeys.publish_unpublishSuccessfully.tr(),
),
(error) => showToastNotification(
context,
message: LocaleKeys.publish_unpublishFailed.tr(),
description: error.msg,
type: ToastificationType.error,
),
);
} else if (state.updatePathNameResult != null) {
state.updatePathNameResult!.onSuccess(
(value) {
showToastNotification(
context,
message:
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
);
context.pop();
},
);
}
}
}
@@ -1,96 +0,0 @@
import 'package:appflowy/plugins/database/application/cell/bloc/media_cell_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_util.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/file/mobile_file_upload_menu.dart';
import 'package:appflowy/util/xfile_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/file_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/media_entities.pbenum.dart';
import 'package:cross_file/cross_file.dart';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
class MobileMediaUploadSheetContent extends StatelessWidget {
const MobileMediaUploadSheetContent({super.key, required this.dialogContext});
final BuildContext dialogContext;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 12),
constraints: const BoxConstraints(
maxHeight: 340,
minHeight: 80,
),
child: MobileFileUploadMenu(
onInsertLocalFile: (files) async {
dialogContext.pop();
await insertLocalFiles(
context,
files,
userProfile: context.read<MediaCellBloc>().state.userProfile,
documentId: context.read<MediaCellBloc>().rowId,
onUploadSuccess: (file, path, isLocalMode) {
final mediaCellBloc = context.read<MediaCellBloc>();
if (mediaCellBloc.isClosed) {
return;
}
mediaCellBloc.add(
MediaCellEvent.addFile(
url: path,
name: file.name,
uploadType: isLocalMode
? FileUploadTypePB.LocalFile
: FileUploadTypePB.CloudFile,
fileType: file.fileType.toMediaFileTypePB(),
),
);
},
);
},
onInsertNetworkFile: (url) async => _onInsertNetworkFile(
url,
dialogContext,
context,
),
),
);
}
Future<void> _onInsertNetworkFile(
String url,
BuildContext dialogContext,
BuildContext context,
) async {
dialogContext.pop();
if (url.isEmpty) return;
final uri = Uri.tryParse(url);
if (uri == null) {
return;
}
final fakeFile = XFile(uri.path);
MediaFileTypePB fileType = fakeFile.fileType.toMediaFileTypePB();
fileType =
fileType == MediaFileTypePB.Other ? MediaFileTypePB.Link : fileType;
String name = uri.pathSegments.isNotEmpty ? uri.pathSegments.last : "";
if (name.isEmpty && uri.pathSegments.length > 1) {
name = uri.pathSegments[uri.pathSegments.length - 2];
} else if (name.isEmpty) {
name = uri.host;
}
context.read<MediaCellBloc>().add(
MediaCellEvent.addFile(
url: url,
name: name,
uploadType: FileUploadTypePB.NetworkFile,
fileType: fileType,
),
);
}
}
@@ -24,7 +24,6 @@ enum MobileViewBottomSheetBodyAction {
copyPublishLink,
visitSite,
copyShareLink,
updatePathName,
}
typedef MobileViewBottomSheetBodyActionCallback = void Function(
@@ -165,15 +164,6 @@ class MobileViewBottomSheetBody extends StatelessWidget {
final isPublished = context.watch<ShareBloc>().state.isPublished;
if (isPublished) {
return [
MobileQuickActionButton(
text: LocaleKeys.shareAction_updatePathName.tr(),
icon: FlowySvgs.view_item_rename_s,
iconSize: const Size.square(18),
onTap: () => onAction(
MobileViewBottomSheetBodyAction.updatePathName,
),
),
_divider(),
MobileQuickActionButton(
text: LocaleKeys.shareAction_visitSite.tr(),
icon: FlowySvgs.m_visit_site_s,
@@ -47,7 +47,6 @@ Future<T?> showMobileBottomSheet<T>(
Color? barrierColor,
double? elevation,
bool showDoneButton = false,
void Function(BuildContext context)? onDone,
bool enableDraggableScrollable = false,
bool enableScrollable = false,
// this field is only used if showDragHandle is true
@@ -113,7 +112,6 @@ Future<T?> showMobileBottomSheet<T>(
showRemoveButton: showRemoveButton,
title: title,
onRemove: onRemove,
onDone: onDone,
),
);
@@ -145,7 +143,6 @@ Future<T?> showMobileBottomSheet<T>(
) ??
Expanded(
child: Scrollbar(
controller: scrollController,
child: SingleChildScrollView(
controller: scrollController,
child: child,
@@ -210,7 +207,6 @@ class BottomSheetHeader extends StatelessWidget {
required this.title,
required this.showDoneButton,
this.onRemove,
this.onDone,
});
final bool showBackButton;
@@ -219,7 +215,6 @@ class BottomSheetHeader extends StatelessWidget {
final String title;
final bool showDoneButton;
final VoidCallback? onRemove;
final void Function(BuildContext context)? onDone;
@override
Widget build(BuildContext context) {
@@ -261,13 +256,7 @@ class BottomSheetHeader extends StatelessWidget {
Align(
alignment: Alignment.centerRight,
child: BottomSheetDoneButton(
onDone: () {
if (onDone != null) {
onDone?.call(context);
} else {
Navigator.pop(context);
}
},
onDone: () => Navigator.pop(context),
),
),
],
@@ -11,7 +11,6 @@ class OptionTextField extends StatelessWidget {
const OptionTextField({
super.key,
required this.controller,
this.autoFocus = false,
required this.isPrimary,
required this.fieldType,
required this.onTextChanged,
@@ -19,7 +18,6 @@ class OptionTextField extends StatelessWidget {
});
final TextEditingController controller;
final bool autoFocus;
final bool isPrimary;
final FieldType fieldType;
final void Function(String value) onTextChanged;
@@ -29,7 +27,6 @@ class OptionTextField extends StatelessWidget {
Widget build(BuildContext context) {
return FlowyOptionTile.textField(
controller: controller,
autofocus: autoFocus,
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
onTextChanged: onTextChanged,
leftIcon: GestureDetector(
@@ -5,7 +5,7 @@ import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_editor
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/mobile_date_picker.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/mobile_appflowy_date_picker.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/mobile_date_header.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -93,19 +93,11 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
onRangeSelected: (start, end) {
dateCellBloc.add(DateCellEditorEvent.updateDateRange(start, end));
},
onIsRangeChanged: (value, dateTime, endDateTime) {
dateCellBloc.add(
DateCellEditorEvent.setIsRange(value, dateTime, endDateTime),
);
onIsRangeChanged: (value) {
dateCellBloc.add(DateCellEditorEvent.setIsRange(value));
},
onIncludeTimeChanged: (value, dateTime, endDateTime) {
dateCellBloc.add(
DateCellEditorEvent.setIncludeTime(
value,
dateTime,
endDateTime,
),
);
onIncludeTimeChanged: (value) {
dateCellBloc.add(DateCellEditorEvent.setIncludeTime(value));
},
onClearDate: () {
dateCellBloc.add(const DateCellEditorEvent.clearDate());
@@ -21,15 +21,15 @@ import 'mobile_quick_field_editor.dart';
const mobileSupportedFieldTypes = [
FieldType.RichText,
FieldType.Number,
FieldType.URL,
FieldType.SingleSelect,
FieldType.MultiSelect,
FieldType.DateTime,
FieldType.Media,
FieldType.URL,
FieldType.Checkbox,
FieldType.Checklist,
FieldType.LastEditedTime,
FieldType.CreatedTime,
FieldType.Checkbox,
FieldType.Checklist,
FieldType.Media,
// FieldType.Time,
];
@@ -9,10 +9,10 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart';
import 'package:appflowy/plugins/database/domain/field_service.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart';
import 'package:appflowy/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:collection/collection.dart';
@@ -224,7 +224,6 @@ class _MobileFieldEditorState extends State<MobileFieldEditor> {
const _Divider(),
OptionTextField(
controller: controller,
autoFocus: widget.mode == FieldOptionMode.add,
fieldType: values.type,
isPrimary: widget.isPrimary,
onTextChanged: (value) {
@@ -863,8 +862,6 @@ class _SelectOptionListState extends State<_SelectOptionList> {
return ListView(
shrinkWrap: true,
padding: EdgeInsets.zero,
// disable the inner scroll physics, so the outer ListView can scroll
physics: const NeverScrollableScrollPhysics(),
children: widget.selectOptions
.mapIndexed(
(index, option) => _SelectOptionTile(
@@ -9,7 +9,6 @@ class MobileDocumentScreen extends StatelessWidget {
this.title,
this.showMoreButton = true,
this.fixedTitle,
this.blockId,
});
/// view id
@@ -17,14 +16,12 @@ class MobileDocumentScreen extends StatelessWidget {
final String? title;
final bool showMoreButton;
final String? fixedTitle;
final String? blockId;
static const routeName = '/docs';
static const viewId = 'id';
static const viewTitle = 'title';
static const viewShowMoreButton = 'show_more_button';
static const viewFixedTitle = 'fixed_title';
static const viewBlockId = 'block_id';
@override
Widget build(BuildContext context) {
@@ -34,7 +31,6 @@ class MobileDocumentScreen extends StatelessWidget {
viewLayout: ViewLayoutPB.Document,
showMoreButton: showMoreButton,
fixedTitle: fixedTitle,
blockId: blockId,
);
}
}
@@ -279,10 +279,16 @@ class _HomePageState extends State<_HomePage> {
ToastificationType toastType = ToastificationType.success;
switch (actionType) {
case UserWorkspaceActionType.open:
message = result.onFailure((e) {
toastType = ToastificationType.error;
return '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}';
});
message = result.fold(
(s) {
toastType = ToastificationType.success;
return LocaleKeys.workspace_openSuccess.tr();
},
(e) {
toastType = ToastificationType.error;
return '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}';
},
);
break;
case UserWorkspaceActionType.delete:
message = result.fold(
@@ -1,3 +0,0 @@
class SpaceUIConstants {
static const itemHeight = 52.0;
}
Loaded 100 of 835 files, more files were not shown because too many files have changed in this diff. Show more