Compare commits
124
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f78b5b4561 | ||
|
|
f6e002edbd | ||
|
|
82effbf8e4 | ||
|
|
54096b391f | ||
|
|
39eee12f53 | ||
|
|
bae19b9c3a | ||
|
|
f00e1ebf20 | ||
|
|
939e28d2e1 | ||
|
|
3fbdcab7b0 | ||
|
|
a1d0dba0a5 | ||
|
|
5e41849f69 | ||
|
|
cf56e20be9 | ||
|
|
1b9b2a5f8d | ||
|
|
690b46a59a | ||
|
|
4d33c87b33 | ||
|
|
7cf0c4b16b | ||
|
|
910b643db0 | ||
|
|
0e5ff844b6 | ||
|
|
a9a784f0f0 | ||
|
|
af390f584e | ||
|
|
eff37d74dc | ||
|
|
0ea2da424f | ||
|
|
07e34609e7 | ||
|
|
74c757d0c8 | ||
|
|
e6357a9d6c | ||
|
|
5a047b9d2d | ||
|
|
873ab6cdc7 | ||
|
|
af6736d352 | ||
|
|
d5c1955ea3 | ||
|
|
8bb8131dd4 | ||
|
|
7f4abb5866 | ||
|
|
fbe87cc536 | ||
|
|
74165ace0f | ||
|
|
b576287ac6 | ||
|
|
68d7211735 | ||
|
|
dd0dcace87 | ||
|
|
8f58c39448 | ||
|
|
cee0e2ed42 | ||
|
|
557ce7e006 | ||
|
|
d50521d9b6 | ||
|
|
28aa2329fb | ||
|
|
9707148b86 | ||
|
|
4e739c857b | ||
|
|
363fecc7d6 | ||
|
|
6bbaad59a4 | ||
|
|
497fcf3bbb | ||
|
|
b9ec9f863d | ||
|
|
eafd0b3353 | ||
|
|
a42b6e02ab | ||
|
|
79116e7c7f | ||
|
|
128ff594be | ||
|
|
c821454df8 | ||
|
|
73e8b476fb | ||
|
|
faa95ffb9c | ||
|
|
6a5c4c95b7 | ||
|
|
7141e9d8cf | ||
|
|
d7bbb4261c | ||
|
|
251b16cb62 | ||
|
|
da80c599bf | ||
|
|
ca6a900874 | ||
|
|
5e09e96f7b | ||
|
|
068500df84 | ||
|
|
15949a7e21 | ||
|
|
e635fe3378 | ||
|
|
8276c5db57 | ||
|
|
1d9876a6eb | ||
|
|
6340174927 | ||
|
|
7b031b228e | ||
|
|
ec408940e4 | ||
|
|
bd46fc11f4 | ||
|
|
42e55fe248 | ||
|
|
3a9011b3cb | ||
|
|
e3bf0442e8 | ||
|
|
0413100e2b | ||
|
|
7cad04bbf4 | ||
|
|
c9f0bbf26a | ||
|
|
d91639172a | ||
|
|
5f0cc1becd | ||
|
|
a8bcab7770 | ||
|
|
b1682e4f54 | ||
|
|
2447b34107 | ||
|
|
8126d19682 | ||
|
|
6cc8c81e3d | ||
|
|
e86d812ee6 | ||
|
|
c6f042830f | ||
|
|
2378c0c441 | ||
|
|
7bc53d7bc6 | ||
|
|
7918d6c0d1 | ||
|
|
073e8b897b | ||
|
|
46a3006f4d | ||
|
|
5d578ab9e6 | ||
|
|
f3fe729900 | ||
|
|
a04c157770 | ||
|
|
320ffcb434 | ||
|
|
37a2fb17b5 | ||
|
|
b5936cec54 | ||
|
|
ea61c81cce | ||
|
|
64d71d0d20 | ||
|
|
af08b4c004 | ||
|
|
ea670b6ae6 | ||
|
|
f19e354418 | ||
|
|
b54e3dd243 | ||
|
|
9798e39e49 | ||
|
|
66fd0c19da | ||
|
|
8d6c67206c | ||
|
|
8cf683eb50 | ||
|
|
580a23f3f5 | ||
|
|
29dc46a993 | ||
|
|
93885bad7b | ||
|
|
6e0619c21e | ||
|
|
8c956afabd | ||
|
|
f9fbf62283 | ||
|
|
5f1f536181 | ||
|
|
ba59514464 | ||
|
|
6bb6750be7 | ||
|
|
19a3df6d47 | ||
|
|
87408fd2a9 | ||
|
|
9ee39f45c9 | ||
|
|
a763304386 | ||
|
|
f8ce501b53 | ||
|
|
54d449647c | ||
|
|
552fd39d74 | ||
|
|
01d73f2753 | ||
|
|
75da496128 |
No files matched your search
@@ -52,7 +52,7 @@ runs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager libmpv-dev mpv
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager
|
||||
shell: bash
|
||||
|
||||
- name: Enable Flutter Desktop
|
||||
@@ -75,4 +75,4 @@ runs:
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
sudo apt-get install network-manager
|
||||
flutter test ${{ inputs.test_path }} -d Linux --coverage
|
||||
shell: bash
|
||||
shell: bash
|
||||
@@ -28,6 +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
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -39,7 +40,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@@ -73,9 +74,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ windows-2019 ]
|
||||
os: [windows-latest]
|
||||
include:
|
||||
- os: windows-2019
|
||||
- os: windows-latest
|
||||
flutter_profile: development-windows-x86
|
||||
target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -100,7 +101,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ macos-latest ]
|
||||
os: [macos-latest]
|
||||
include:
|
||||
- os: macos-latest
|
||||
flutter_profile: development-mac-x86_64
|
||||
@@ -122,12 +123,12 @@ jobs:
|
||||
flutter_profile: ${{ matrix.flutter_profile }}
|
||||
|
||||
unit_test:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@@ -173,7 +174,7 @@ jobs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
@@ -216,11 +217,11 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
cloud_integration_test:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@@ -247,9 +248,9 @@ jobs:
|
||||
- name: Run Docker-Compose
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.29-amd64
|
||||
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
|
||||
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
|
||||
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
run: |
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
@@ -261,11 +262,26 @@ jobs:
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Restarting with the correct version..."
|
||||
# Remove all containers if any exist
|
||||
if [ "$(docker ps -aq)" ]; then
|
||||
docker rm -f $(docker ps -aq)
|
||||
else
|
||||
echo "No containers to remove."
|
||||
fi
|
||||
|
||||
# Remove all volumes if any exist
|
||||
if [ "$(docker volume ls -q)" ]; then
|
||||
docker volume rm $(docker volume ls -q)
|
||||
else
|
||||
echo "No volumes to remove."
|
||||
fi
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
docker ps -a
|
||||
docker compose logs
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
@@ -292,7 +308,7 @@ jobs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
shell: bash
|
||||
|
||||
- name: Enable Flutter Desktop
|
||||
@@ -323,14 +339,14 @@ jobs:
|
||||
flutter test integration_test/desktop/cloud/cloud_runner.dart -d Linux --coverage
|
||||
shell: bash
|
||||
|
||||
# split the integration tests into different machines to minimize the time
|
||||
integration_test_1:
|
||||
needs: [ prepare-linux ]
|
||||
integration_test:
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
test_number: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
@@ -339,76 +355,10 @@ jobs:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 1
|
||||
- name: Flutter Integration Test ${{ matrix.test_number }}
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
test_path: integration_test/desktop_runner_1.dart
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_2:
|
||||
needs: [ prepare-linux ]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 2
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
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: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 3
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
test_path: integration_test/desktop_runner_3.dart
|
||||
test_path: integration_test/desktop_runner_${{ matrix.test_number }}.dart
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
|
||||
@@ -22,6 +22,7 @@ on:
|
||||
env:
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
CLOUD_VERSION: 0.6.51
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -48,13 +49,13 @@ jobs:
|
||||
model: "iPhone 15"
|
||||
shutdown_after_job: false
|
||||
|
||||
build-macos:
|
||||
integration-tests:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -96,11 +97,11 @@ jobs:
|
||||
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
|
||||
cargo make --profile development-ios-arm64-sim code_generation
|
||||
|
||||
- uses: futureware-tech/simulator-action@v3
|
||||
id: simulator-action
|
||||
with:
|
||||
model: "iPhone 15"
|
||||
shutdown_after_job: false
|
||||
# - uses: futureware-tech/simulator-action@v3
|
||||
# id: simulator-action
|
||||
# with:
|
||||
# model: "iPhone 15"
|
||||
# shutdown_after_job: false
|
||||
|
||||
# - name: Run AppFlowy on simulator
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
@@ -111,7 +112,6 @@ jobs:
|
||||
# kill $pid
|
||||
# continue-on-error: true
|
||||
|
||||
# enable it again if the 12 mins timeout is fixed
|
||||
# - name: Run integration tests
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
# run: flutter test integration_test/runner.dart -d ${{ steps.simulator-action.outputs.udid }}
|
||||
@@ -368,7 +368,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||
sudo apt-get install keybinder-3.0
|
||||
sudo apt-get install -y alien libnotify-dev libmpv-dev mpv
|
||||
sudo apt-get install -y alien libnotify-dev
|
||||
source $HOME/.cargo/env
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
|
||||
@@ -20,6 +20,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CLOUD_VERSION: 0.6.51-amd64
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
|
||||
jobs:
|
||||
@@ -46,9 +47,9 @@ jobs:
|
||||
- name: Ensure AppFlowy-Cloud is Running with Correct Version
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
|
||||
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
|
||||
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
run: |
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
@@ -65,6 +66,8 @@ jobs:
|
||||
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
|
||||
@@ -93,6 +96,11 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set timezone for action
|
||||
uses: szenius/set-timezone@v2.0
|
||||
with:
|
||||
timezoneLinux: "US/Pacific"
|
||||
|
||||
- name: Maximize build space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
@@ -135,30 +143,31 @@ jobs:
|
||||
- name: Ensure AppFlowy-Cloud is Running with Correct Version
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
|
||||
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
|
||||
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
|
||||
run: |
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
# Remove all containers if any exist
|
||||
if [ "$(docker ps -aq)" ]; then
|
||||
docker rm -f $(docker ps -aq)
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
echo "No containers to remove."
|
||||
fi
|
||||
|
||||
# Remove all volumes if any exist
|
||||
if [ "$(docker volume ls -q)" ]; then
|
||||
docker volume rm $(docker volume ls -q)
|
||||
else
|
||||
echo "No volumes to remove."
|
||||
fi
|
||||
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
docker ps -a
|
||||
docker compose logs
|
||||
|
||||
- name: Run rust-lib tests
|
||||
working-directory: frontend/rust-lib
|
||||
env:
|
||||
|
||||
+34
-1
@@ -1,4 +1,37 @@
|
||||
# Release Notes
|
||||
## Version 0.7.2 - 22/10/2024
|
||||
### New Features
|
||||
- Copy link to block
|
||||
- Support turn into in document
|
||||
- Enable sharing links and publishing pages on mobile
|
||||
- Enable drag and drop in row documents
|
||||
- Right-click on page in sidebar to open more actions
|
||||
- Create new subpage in document using `+` character
|
||||
- Allow reordering checklist item
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed issue with inability to cancel inline code format in French IME
|
||||
- Fixed delete with Shift or Ctrl shortcuts not working in documents
|
||||
- Fixed the issues with incorrect time zone being used in filters.
|
||||
|
||||
## Version 0.7.1 - 07/10/2024
|
||||
### New Features
|
||||
- Copy link to share and open it in a browser
|
||||
- Enable the ability to edit the page title within the body of the document
|
||||
- Filter by last modified, created at, or a date range
|
||||
- Allow customization of database property icons
|
||||
- Support CTRL/CMD+X to delete the current line when the selection is collapsed in the document
|
||||
- Support window tiling on macOS
|
||||
- Add filters to grid views on mobile
|
||||
- Create and manage workspaces on mobile
|
||||
- Automatically convert property types for imported CSV files
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed calculations with filters applied
|
||||
- Fixed issues with importing data folders into a cloud account
|
||||
- Fixed French IME backtick issues
|
||||
- Fixed selection gesture bugs on mobile
|
||||
|
||||
## Version 0.7.0 - 19/09/2024
|
||||
### New Features
|
||||
- Support reordering blocks in document with drag and drop
|
||||
@@ -42,7 +75,7 @@
|
||||
- Fixed the inability to edit group names on Kanban boards
|
||||
- Made error codes more user-friendly
|
||||
- Added leading zeros to day and month in date format
|
||||
|
||||
|
||||
## Version 0.6.8 - 22/08/2024
|
||||
### New Features
|
||||
- Enabled viewing data inside a database record on mobile.
|
||||
|
||||
@@ -119,7 +119,7 @@ We do not claim to outperform Notion in terms of functionality and design, at le
|
||||
|
||||
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information.
|
||||
|
||||
## Acknowledgements
|
||||
## Acknowledgments
|
||||
|
||||
Special thanks to these amazing projects which help power AppFlowy.IO:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.7.1"
|
||||
APPFLOWY_VERSION = "0.7.3"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
@@ -16,17 +6,6 @@ analyzer:
|
||||
- "**/*.freezed.dart"
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
- require_trailing_commas
|
||||
|
||||
@@ -51,8 +30,5 @@ linter:
|
||||
- sort_constructors_first
|
||||
- unawaited_futures
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
@@ -4,7 +4,6 @@ import 'package:appflowy/plugins/database/board/presentation/widgets/board_colum
|
||||
import 'package:appflowy/plugins/database/board/presentation/widgets/board_hidden_groups.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -121,22 +120,3 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension FlowySvgFinder on CommonFinders {
|
||||
Finder byFlowySvg(FlowySvgData svg) => _FlowySvgFinder(svg);
|
||||
}
|
||||
|
||||
class _FlowySvgFinder extends MatchFinder {
|
||||
_FlowySvgFinder(this.svg);
|
||||
|
||||
final FlowySvgData svg;
|
||||
|
||||
@override
|
||||
String get description => 'flowy_svg "$svg"';
|
||||
|
||||
@override
|
||||
bool matches(Element candidate) {
|
||||
final Widget widget = candidate.widget;
|
||||
return widget is FlowySvg && widget.svg == svg;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:time/time.dart';
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/util.dart';
|
||||
@@ -14,6 +15,31 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('board row test', () {
|
||||
testWidgets('edit item in ToDo card', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||
const name = 'Card 1';
|
||||
final card1 = find.ancestor(
|
||||
matching: find.byType(RowCard),
|
||||
of: find.text(name),
|
||||
);
|
||||
await tester.hoverOnWidget(
|
||||
card1,
|
||||
onHover: () async {
|
||||
final editCard = find.byType(EditCardAccessory);
|
||||
await tester.tapButton(editCard);
|
||||
},
|
||||
);
|
||||
await tester.showKeyboard(card1);
|
||||
tester.testTextInput.enterText("");
|
||||
await tester.pump(300.milliseconds);
|
||||
tester.testTextInput.enterText("a");
|
||||
await tester.pump(300.milliseconds);
|
||||
expect(find.text('a'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('delete item in ToDo card', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
@@ -3,9 +3,13 @@ import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
|
||||
import 'uncategorized/uncategorized_test_runner.dart'
|
||||
as uncategorized_test_runner;
|
||||
import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
|
||||
import 'data_migration/data_migration_test_runner.dart' as data_migration_test_runner;
|
||||
import 'data_migration/data_migration_test_runner.dart'
|
||||
as data_migration_test_runner;
|
||||
import 'set_env.dart' as preset_af_cloud_env_test;
|
||||
|
||||
Future<void> main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
|
||||
data_migration_test_runner.main();
|
||||
// uncategorized
|
||||
uncategorized_test_runner.main();
|
||||
@@ -18,5 +22,4 @@ Future<void> main() async {
|
||||
|
||||
// sidebar
|
||||
sidebar_move_page_test.main();
|
||||
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/document_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// copy link to block
|
||||
group('copy link to block:', () {
|
||||
testWidgets('copy link to check if the clipboard has the correct content',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check the clipboard
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text,
|
||||
matches(appflowySharePageLinkPattern),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('copy link to block(another page) and paste it in doc',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
|
||||
// create a new page and paste it
|
||||
const pageName = 'copy link to block';
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// paste the link to the new page
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.paste();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
final node = tester.editor.getNodeAtPath([0]);
|
||||
final delta = node.delta!;
|
||||
final insert = (delta.first as TextInsert).text;
|
||||
final attributes = delta.first.attributes;
|
||||
expect(insert, MentionBlockKeys.mentionChar);
|
||||
final mention =
|
||||
attributes?[MentionBlockKeys.mention] as Map<String, dynamic>;
|
||||
expect(mention[MentionBlockKeys.type], MentionType.page.name);
|
||||
expect(mention[MentionBlockKeys.blockId], isNotNull);
|
||||
expect(mention[MentionBlockKeys.pageId], isNotNull);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
Constants.gettingStartedPageName,
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
// the pasted block content is 'Welcome to AppFlowy'
|
||||
'Welcome to AppFlowy',
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// tap the mention block to jump to the page
|
||||
await tester.tapButton(find.byType(MentionPageBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to go to the getting started page
|
||||
final documentPage = find.byType(DocumentPage);
|
||||
expect(documentPage, findsOneWidget);
|
||||
expect(
|
||||
tester.widget<DocumentPage>(documentPage).view.name,
|
||||
Constants.gettingStartedPageName,
|
||||
);
|
||||
// and the block is selected
|
||||
expect(
|
||||
tester.widget<DocumentPage>(documentPage).initialBlockId,
|
||||
mention[MentionBlockKeys.blockId],
|
||||
);
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('copy link to block(same page) and paste it in doc',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// create a new page and paste it
|
||||
const pageName = 'copy link to block';
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// copy the link to block from the first line
|
||||
const inputText = 'Hello World';
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText(inputText);
|
||||
await tester.ime.insertCharacter('\n');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
|
||||
// paste the link to the second line
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
await tester.editor.paste();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
final node = tester.editor.getNodeAtPath([1]);
|
||||
final delta = node.delta!;
|
||||
final insert = (delta.first as TextInsert).text;
|
||||
final attributes = delta.first.attributes;
|
||||
expect(insert, MentionBlockKeys.mentionChar);
|
||||
final mention =
|
||||
attributes?[MentionBlockKeys.mention] as Map<String, dynamic>;
|
||||
expect(mention[MentionBlockKeys.type], MentionType.page.name);
|
||||
expect(mention[MentionBlockKeys.blockId], isNotNull);
|
||||
expect(mention[MentionBlockKeys.pageId], isNotNull);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
inputText,
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// edit the pasted block
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('!');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.textContaining(
|
||||
'$inputText!',
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
// tap the mention block
|
||||
await tester.tapButton(find.byType(MentionPageBlock));
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('''1. copy link to block from another page
|
||||
2. paste the link to the new page
|
||||
3. delete the original page
|
||||
4. check the content of the block, it should be no access to the page
|
||||
''', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.copyLinkToBlock([0]);
|
||||
|
||||
// create a new page and paste it
|
||||
const pageName = 'copy link to block';
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// paste the link to the new page
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.paste();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// tap the mention block to jump to the page
|
||||
await tester.tapButton(find.byType(MentionPageBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to go to the getting started page
|
||||
final documentPage = find.byType(DocumentPage);
|
||||
expect(documentPage, findsOneWidget);
|
||||
expect(
|
||||
tester.widget<DocumentPage>(documentPage).view.name,
|
||||
Constants.gettingStartedPageName,
|
||||
);
|
||||
// delete the getting started page
|
||||
await tester.hoverOnPageName(
|
||||
Constants.gettingStartedPageName,
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
tester.expectToSeeDocumentBanner();
|
||||
tester.expectNotToSeePageName(gettingStarted);
|
||||
|
||||
// delete the page permanently
|
||||
await tester.tapDeletePermanentlyButton();
|
||||
|
||||
// go back the page
|
||||
await tester.openPage(pageName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check the content of the block
|
||||
// it should be no access to the page
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
matching: find.findTextInFlowyText(
|
||||
LocaleKeys.document_mention_noAccess.tr(),
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
-32
@@ -68,38 +68,6 @@ void main() {
|
||||
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
|
||||
});
|
||||
|
||||
// Copy link to block feature is disable temporarily, enable this test when the feature is ready.
|
||||
// testWidgets('copy block link', (tester) async {
|
||||
// await tester.initializeAppFlowy(
|
||||
// cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
// );
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
// await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// // open getting started page
|
||||
// await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// // hover and click on the option menu button beside the block component.
|
||||
// await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
// // click the copy link to block option
|
||||
// await tester.tap(
|
||||
// find.findTextInFlowyText(
|
||||
// LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
|
||||
// ),
|
||||
// );
|
||||
// await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// // check the clipboard
|
||||
// final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
// expect(
|
||||
// content?.text,
|
||||
// matches(
|
||||
// r'^https:\/\/appflowy\.com\/app\/[a-f0-9-]{36}\/[a-f0-9-]{36}\?blockId=[A-Za-z0-9_-]+$',
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
|
||||
testWidgets('hover on the block and delete it', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
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/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_tab.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_menu.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/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/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.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);
|
||||
|
||||
await tester.tap(inputField);
|
||||
|
||||
// input the new path name
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+5
@@ -1,9 +1,14 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_copy_link_to_block_test.dart'
|
||||
as document_copy_link_to_block_test;
|
||||
import 'document_option_actions_test.dart' as document_option_actions_test;
|
||||
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();
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../../shared/util.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
// This test is meaningless, just for preventing the CI from failing.
|
||||
void main() {
|
||||
+9
@@ -18,6 +18,7 @@ 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';
|
||||
@@ -92,6 +93,14 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
// Attempt right-click on the page name and expect not to see
|
||||
await tester.tap(gettingStarted.last, buttons: kSecondaryButton);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.text(LocaleKeys.disclosureAction_moveTo.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// move the current page to Getting started
|
||||
await tester.tapButton(
|
||||
gettingStarted.last,
|
||||
|
||||
-2
@@ -1,9 +1,7 @@
|
||||
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
||||
import 'empty_test.dart' as preset_af_cloud_env_test;
|
||||
import 'user_setting_sync_test.dart' as user_sync_test;
|
||||
|
||||
void main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
appflowy_cloud_auth_test.main();
|
||||
user_sync_test.main();
|
||||
}
|
||||
+3
-2
@@ -6,6 +6,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
|
||||
import 'package:appflowy/plugins/shared/share/constants.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_menu.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
@@ -61,11 +62,11 @@ void main() {
|
||||
final plainText = clipboardContent.plainText;
|
||||
expect(
|
||||
plainText,
|
||||
startsWith(ShareConstants.shareBaseUrl),
|
||||
matches(appflowySharePageLinkPattern),
|
||||
);
|
||||
|
||||
final shareValues = plainText!
|
||||
.replaceAll('${ShareConstants.shareBaseUrl}/', '')
|
||||
.replaceAll('https://${ShareConstants.shareBaseUrl}/', '')
|
||||
.split('/');
|
||||
final workspaceId = shareValues[0];
|
||||
expect(workspaceId, isNotEmpty);
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('workspace icon:', () {
|
||||
testWidgets('remove icon from workspace', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
await tester.openWorkspaceMenu();
|
||||
|
||||
// click the workspace icon
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(WorkspaceMenuItem),
|
||||
matching: find.byType(WorkspaceIcon),
|
||||
),
|
||||
);
|
||||
// click the remove icon button
|
||||
await tester.tapButton(
|
||||
find.text(LocaleKeys.button_remove.tr()),
|
||||
);
|
||||
|
||||
// nothing should happen
|
||||
expect(
|
||||
find.text(LocaleKeys.workspace_updateIconSuccess.tr()),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+272
@@ -4,8 +4,10 @@ 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';
|
||||
@@ -15,6 +17,13 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sid
|
||||
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_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/home_page_menu.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';
|
||||
@@ -27,6 +36,7 @@ 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';
|
||||
@@ -93,4 +103,266 @@ void main() {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('sites settings:', () {
|
||||
testWidgets(
|
||||
'manage published page, set it as homepage, remove the homepage',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const pageName = 'Document';
|
||||
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// open the publish menu
|
||||
await tester.openPublishMenu();
|
||||
|
||||
// publish the document
|
||||
await tester.tapButton(find.byType(PublishButton));
|
||||
|
||||
// click empty area to close the publish menu
|
||||
await tester.tapAt(Offset.zero);
|
||||
await tester.pumpAndSettle();
|
||||
// check if the page is published in sites page
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.sites);
|
||||
// wait the backend return the sites data
|
||||
await tester.wait(1000);
|
||||
|
||||
// check if the page is published in sites page
|
||||
final pageItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is PublishedViewItem &&
|
||||
widget.publishInfoView.view.name == pageName,
|
||||
);
|
||||
expect(pageItem, findsOneWidget);
|
||||
|
||||
// comment it out because it's not allowed to update the namespace in free plan
|
||||
// // set it to homepage
|
||||
// await tester.tapButton(
|
||||
// find.textContaining(
|
||||
// LocaleKeys.settings_sites_selectHomePage.tr(),
|
||||
// ),
|
||||
// );
|
||||
// await tester.tapButton(
|
||||
// find.descendant(
|
||||
// of: find.byType(SelectHomePageMenu),
|
||||
// matching: find.text(pageName),
|
||||
// ),
|
||||
// );
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
// // check if the page is set to homepage
|
||||
// final homePageItem = find.descendant(
|
||||
// of: find.byType(DomainItem),
|
||||
// matching: find.text(pageName),
|
||||
// );
|
||||
// expect(homePageItem, findsOneWidget);
|
||||
|
||||
// // remove the homepage
|
||||
// await tester.tapButton(find.byType(DomainMoreAction));
|
||||
// await tester.tapButton(
|
||||
// find.text(LocaleKeys.settings_sites_removeHomepage.tr()),
|
||||
// );
|
||||
// await tester.pumpAndSettle();
|
||||
|
||||
// // check if the page is removed from homepage
|
||||
// expect(homePageItem, findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('update namespace', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// check if the page is published in sites page
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.sites);
|
||||
// wait the backend return the sites data
|
||||
await tester.wait(1000);
|
||||
|
||||
// update the domain
|
||||
final domainMoreAction = find.byType(DomainMoreAction);
|
||||
await tester.tapButton(domainMoreAction);
|
||||
final updateNamespaceButton = find.text(
|
||||
LocaleKeys.settings_sites_updateNamespace.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(updateNamespaceButton);
|
||||
|
||||
// click the update namespace button
|
||||
|
||||
await tester.tapButton(updateNamespaceButton);
|
||||
|
||||
// comment it out because it's not allowed to update the namespace in free plan
|
||||
// expect to see the dialog
|
||||
// await tester.updateNamespace('&&&???');
|
||||
|
||||
// // need to upgrade to pro plan to update the namespace
|
||||
// final errorToast = find.text(
|
||||
// LocaleKeys.settings_sites_error_proPlanLimitation.tr(),
|
||||
// );
|
||||
// await tester.pumpUntilFound(errorToast);
|
||||
// expect(errorToast, findsOneWidget);
|
||||
// await tester.pumpUntilNotFound(errorToast);
|
||||
|
||||
// comment it out because it's not allowed to update the namespace in free plan
|
||||
// // short namespace
|
||||
// await tester.updateNamespace('a');
|
||||
|
||||
// // expect to see the toast with error message
|
||||
// final errorToast2 = find.text(
|
||||
// LocaleKeys.settings_sites_error_namespaceTooShort.tr(),
|
||||
// );
|
||||
// await tester.pumpUntilFound(errorToast2);
|
||||
// expect(errorToast2, findsOneWidget);
|
||||
// await tester.pumpUntilNotFound(errorToast2);
|
||||
// // valid namespace
|
||||
// await tester.updateNamespace('AppFlowy');
|
||||
|
||||
// // expect to see the toast with success message
|
||||
// final successToast = find.text(
|
||||
// LocaleKeys.settings_sites_success_namespaceUpdated.tr(),
|
||||
// );
|
||||
// await tester.pumpUntilFound(successToast);
|
||||
// expect(successToast, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('''
|
||||
More actions for published page:
|
||||
1. visit site
|
||||
2. copy link
|
||||
3. settings
|
||||
4. unpublish
|
||||
5. custom url
|
||||
''', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const pageName = 'Document';
|
||||
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// open the publish menu
|
||||
await tester.openPublishMenu();
|
||||
|
||||
// publish the document
|
||||
await tester.tapButton(find.byType(PublishButton));
|
||||
|
||||
// click empty area to close the publish menu
|
||||
await tester.tapAt(Offset.zero);
|
||||
await tester.pumpAndSettle();
|
||||
// check if the page is published in sites page
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.sites);
|
||||
// wait the backend return the sites data
|
||||
await tester.wait(1000);
|
||||
|
||||
// check if the page is published in sites page
|
||||
final pageItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is PublishedViewItem &&
|
||||
widget.publishInfoView.view.name == pageName,
|
||||
);
|
||||
expect(pageItem, findsOneWidget);
|
||||
|
||||
final copyLinkItem = find.text(LocaleKeys.shareAction_copyLink.tr());
|
||||
final customUrlItem = find.text(LocaleKeys.settings_sites_customUrl.tr());
|
||||
final unpublishItem = find.text(LocaleKeys.shareAction_unPublish.tr());
|
||||
|
||||
// custom url
|
||||
final publishMoreAction = find.byType(PublishedViewMoreAction);
|
||||
|
||||
// click the copy link button
|
||||
{
|
||||
await tester.tapButton(publishMoreAction);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilFound(copyLinkItem);
|
||||
await tester.tapButton(copyLinkItem);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilNotFound(copyLinkItem);
|
||||
|
||||
final clipboardContent = await getIt<ClipboardService>().getData();
|
||||
final plainText = clipboardContent.plainText;
|
||||
expect(
|
||||
plainText,
|
||||
contains(pageName),
|
||||
);
|
||||
}
|
||||
|
||||
// custom url
|
||||
{
|
||||
await tester.tapButton(publishMoreAction);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilFound(customUrlItem);
|
||||
await tester.tapButton(customUrlItem);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilNotFound(customUrlItem);
|
||||
|
||||
// see the custom url dialog
|
||||
final customUrlDialog = find.byType(PublishedViewSettingsDialog);
|
||||
expect(customUrlDialog, findsOneWidget);
|
||||
|
||||
// rename the custom url
|
||||
final textField = find.descendant(
|
||||
of: customUrlDialog,
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await tester.enterText(textField, 'hello-world');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// click the save button
|
||||
final saveButton = find.descendant(
|
||||
of: customUrlDialog,
|
||||
matching: find.text(LocaleKeys.button_save.tr()),
|
||||
);
|
||||
await tester.tapButton(saveButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// expect to see the toast with success message
|
||||
final successToast = find.text(
|
||||
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(successToast);
|
||||
expect(successToast, findsOneWidget);
|
||||
}
|
||||
|
||||
// unpublish
|
||||
{
|
||||
await tester.tapButton(publishMoreAction);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilFound(unpublishItem);
|
||||
await tester.tapButton(unpublishItem);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpUntilNotFound(unpublishItem);
|
||||
|
||||
// expect to see the toast with success message
|
||||
final successToast = find.text(
|
||||
LocaleKeys.publish_unpublishSuccessfully.tr(),
|
||||
);
|
||||
await tester.pumpUntilFound(successToast);
|
||||
expect(successToast, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(successToast);
|
||||
|
||||
// check if the page is unpublished in sites page
|
||||
expect(pageItem, findsNothing);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+2
@@ -3,6 +3,7 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'change_name_and_icon_test.dart' as change_name_and_icon_test;
|
||||
import 'collaborative_workspace_test.dart' as collaborative_workspace_test;
|
||||
import 'share_menu_test.dart' as share_menu_test;
|
||||
import 'workspace_icon_test.dart' as workspace_icon_test;
|
||||
import 'workspace_settings_test.dart' as workspace_settings_test;
|
||||
|
||||
void main() {
|
||||
@@ -12,4 +13,5 @@ void main() {
|
||||
share_menu_test.main();
|
||||
collaborative_workspace_test.main();
|
||||
change_name_and_icon_test.main();
|
||||
workspace_icon_test.main();
|
||||
}
|
||||
+68
-1
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_editor.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -277,5 +277,72 @@ void main() {
|
||||
|
||||
tester.assertRowDetailPageOpened();
|
||||
});
|
||||
|
||||
testWidgets('filter calendar events', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// Create the calendar view
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Calendar,
|
||||
);
|
||||
|
||||
// Create a new event on the first of this month
|
||||
final today = DateTime.now();
|
||||
final firstOfThisMonth = DateTime(today.year, today.month);
|
||||
await tester.doubleClickCalendarCell(firstOfThisMonth);
|
||||
await tester.dismissEventEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
await tester.openCalendarEvent(index: 0, date: firstOfThisMonth);
|
||||
await tester.tapButton(finderForFieldType(FieldType.MultiSelect));
|
||||
await tester.createOption(name: "asdf");
|
||||
await tester.createOption(name: "qwer");
|
||||
await tester.selectOption(name: "asdf");
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(FieldType.MultiSelect, "Tags");
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.tapOptionFilterWithName('asdf');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(0);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.tapOptionFilterWithName('asdf');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.tapOptionFilterWithName('asdf');
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(0);
|
||||
|
||||
final secondOfThisMonth = DateTime(today.year, today.month, 2);
|
||||
await tester.doubleClickCalendarCell(secondOfThisMonth);
|
||||
await tester.dismissEventEditor();
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
await tester.openCalendarEvent(index: 0, date: secondOfThisMonth);
|
||||
await tester.tapButton(finderForFieldType(FieldType.MultiSelect));
|
||||
await tester.selectOption(name: "asdf");
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(0);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Tags');
|
||||
await tester.changeSelectFilterCondition(
|
||||
SelectOptionFilterConditionPB.OptionIsEmpty,
|
||||
);
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
tester.assertNumberOfEventsOnSpecificDay(1, secondOfThisMonth);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -211,7 +211,7 @@ void main() {
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
// Select a date
|
||||
final now = DateTime.now();
|
||||
DateTime now = DateTime.now();
|
||||
await tester.selectDay(content: now.day);
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
@@ -225,7 +225,7 @@ void main() {
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
|
||||
// Toggle include time
|
||||
// When toggling include time, the time value is from the previous existing date time, not the current time
|
||||
now = DateTime.now();
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
+27
-3
@@ -1,11 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/field/type_option_editor/select/select_option.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -316,6 +317,30 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('text in viewport while typing', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
await tester.changeCalculateAtIndex(0, CalculationType.Count);
|
||||
|
||||
// add very large text with 200 lines
|
||||
final largeText = List.generate(
|
||||
200,
|
||||
(index) => 'Line ${index + 1}',
|
||||
).join('\n');
|
||||
|
||||
await tester.editCell(
|
||||
rowIndex: 2,
|
||||
fieldType: FieldType.RichText,
|
||||
input: largeText,
|
||||
);
|
||||
|
||||
// checks if last line is in view port
|
||||
tester.expectToSeeText('Line 200');
|
||||
});
|
||||
|
||||
// Disable this test because it fails on CI randomly
|
||||
// testWidgets('last modified and created at field type options',
|
||||
// (tester) async {
|
||||
@@ -557,7 +582,6 @@ void main() {
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('MMM dd, y').format(now),
|
||||
// content: DateFormat('MMM dd, y HH:mm').format(now),
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 1,
|
||||
|
||||
+35
-10
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -108,7 +108,7 @@ void main() {
|
||||
await tester.tapOptionFilterWithName('s4');
|
||||
|
||||
// The row with 's4' should be shown.
|
||||
tester.assertNumberOfRowsInGridPage(1);
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
@@ -154,12 +154,10 @@ void main() {
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
|
||||
await tester.tapFilterButtonInGrid('date');
|
||||
await tester.tapDateFilterButtonInGrid();
|
||||
await tester.tapDateFilterCondition(DateTimeFilterCondition.before);
|
||||
await tester.changeDateFilterCondition(DateTimeFilterCondition.before);
|
||||
tester.assertNumberOfRowsInGridPage(7);
|
||||
|
||||
await tester.tapDateFilterButtonInGrid();
|
||||
await tester.tapDateFilterCondition(DateTimeFilterCondition.isEmpty);
|
||||
await tester.changeDateFilterCondition(DateTimeFilterCondition.isEmpty);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
@@ -171,8 +169,6 @@ void main() {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
|
||||
await tester.createField(
|
||||
FieldType.CreatedTime,
|
||||
name: 'Created at',
|
||||
@@ -189,11 +185,40 @@ void main() {
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Created at');
|
||||
await tester.tapDateFilterButtonInGrid();
|
||||
await tester.tapDateFilterCondition(DateTimeFilterCondition.before);
|
||||
await tester.changeDateFilterCondition(DateTimeFilterCondition.before);
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('create new row when filters don\'t autofill', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// create a filter
|
||||
await tester.tapDatabaseFilterButton();
|
||||
await tester.tapCreateFilterByFieldType(
|
||||
FieldType.RichText,
|
||||
'Name',
|
||||
);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.tapCreateRowButtonInGrid();
|
||||
tester.assertNumberOfRowsInGridPage(4);
|
||||
|
||||
await tester.tapFilterButtonInGrid('Name');
|
||||
await tester
|
||||
.changeTextFilterCondition(TextFilterConditionPB.TextIsNotEmpty);
|
||||
await tester.dismissCellEditor();
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
|
||||
await tester.tapCreateRowButtonInGrid();
|
||||
tester.assertNumberOfRowsInGridPage(0);
|
||||
expect(find.byType(RowDetailPage), findsOneWidget);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
});
|
||||
}
|
||||
+18
-33
@@ -21,7 +21,6 @@ import 'package:path_provider/path_provider.dart';
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -69,10 +68,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect one file
|
||||
expect(find.byType(RenderMedia), findsOneWidget);
|
||||
@@ -85,9 +81,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.tapFileUploadHint();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect two files
|
||||
@@ -139,10 +133,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect two files
|
||||
expect(find.byType(RenderMedia), findsNWidgets(2));
|
||||
@@ -193,10 +184,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect two files
|
||||
expect(find.byType(RenderMedia), findsNWidgets(2));
|
||||
@@ -230,7 +218,7 @@ void main() {
|
||||
await Future.wait([firstFile.delete(), secondFile.delete()]);
|
||||
});
|
||||
|
||||
testWidgets('hide file names', (tester) async {
|
||||
testWidgets('show file names', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
@@ -272,10 +260,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect two files
|
||||
expect(find.byType(RenderMedia), findsNWidgets(2));
|
||||
@@ -283,28 +268,28 @@ void main() {
|
||||
await tester.dismissCellEditor();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Open first row in row detail view then toggle hide file names
|
||||
// Open first row in row detail view then toggle show file names
|
||||
await tester.openFirstRowDetailPage();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect file names to not be shown (hidden)
|
||||
expect(find.text('sample.jpeg'), findsNothing);
|
||||
expect(find.text('sample.gif'), findsNothing);
|
||||
|
||||
await tester.tapGridFieldWithNameInRowDetailPage('Type');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Toggle show file names
|
||||
await tester.tap(find.byType(Toggle));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect file names to be shown
|
||||
expect(find.text('sample.jpeg'), findsOneWidget);
|
||||
expect(find.text('sample.gif'), findsOneWidget);
|
||||
|
||||
await tester.tapGridFieldWithNameInRowDetailPage('Type');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Toggle hide file names
|
||||
await tester.tap(find.byType(Toggle));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.dismissRowDetailPage();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect file names to be hidden
|
||||
expect(find.text('sample.jpeg'), findsNothing);
|
||||
expect(find.text('sample.gif'), findsNothing);
|
||||
|
||||
// Remove the temp files
|
||||
await Future.wait([firstFile.delete(), secondFile.delete()]);
|
||||
});
|
||||
|
||||
+1
-3
@@ -62,9 +62,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.tapFileUploadHint();
|
||||
|
||||
// Expect one file
|
||||
expect(find.byType(RenderMedia), findsOneWidget);
|
||||
|
||||
+138
-1
@@ -1,3 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -9,7 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -335,5 +338,139 @@ void main() {
|
||||
|
||||
tester.assertNumberOfRowsInGridPage(4);
|
||||
});
|
||||
|
||||
testWidgets('edit checklist cell', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
const fieldType = FieldType.Checklist;
|
||||
await tester.createField(fieldType);
|
||||
|
||||
await tester.openFirstRowDetailPage();
|
||||
await tester.hoverOnWidget(
|
||||
find.byType(ChecklistRowDetailCell),
|
||||
onHover: () async {
|
||||
await tester.tapButton(find.byType(ChecklistItemControl));
|
||||
},
|
||||
);
|
||||
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 0);
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 1');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 1);
|
||||
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 2');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 2);
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(PhantomChecklistItem), findsNothing);
|
||||
|
||||
await tester.renameChecklistTask(index: 0, name: "task -1", enter: false);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task -1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 1);
|
||||
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 0');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 2);
|
||||
|
||||
await tester.checkChecklistTask(index: 1);
|
||||
expect(find.byType(PhantomChecklistItem), findsNothing);
|
||||
expect(find.byType(ChecklistItem), findsNWidgets(3));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task -1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 0",
|
||||
isChecked: true,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 2,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(ProgressAndHideCompleteButton),
|
||||
matching: find.byType(FlowyIconButton),
|
||||
),
|
||||
);
|
||||
expect(find.byType(ChecklistItem), findsNWidgets(2));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task -1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
await tester.renameChecklistTask(index: 1, name: "task 3", enter: false);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
await tester.renameChecklistTask(index: 0, name: "task 1", enter: false);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
await tester.enterText(find.byType(PhantomChecklistItem), 'task 2');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 1",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 1,
|
||||
name: "task 2",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 2,
|
||||
name: "task 3",
|
||||
isChecked: false,
|
||||
);
|
||||
tester.assertPhantomChecklistItemAtIndex(index: 2);
|
||||
|
||||
await tester.checkChecklistTask(index: 1);
|
||||
expect(find.byType(ChecklistItem), findsNWidgets(2));
|
||||
});
|
||||
});
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'database_cell_test.dart' as database_cell_test;
|
||||
import 'database_field_settings_test.dart' as database_field_settings_test;
|
||||
import 'database_field_test.dart' as database_field_test;
|
||||
import 'database_row_page_test.dart' as database_row_page_test;
|
||||
import 'database_setting_test.dart' as database_setting_test;
|
||||
import 'database_share_test.dart' as database_share_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
database_cell_test.main();
|
||||
database_field_test.main();
|
||||
database_field_settings_test.main();
|
||||
database_share_test.main();
|
||||
database_row_page_test.main();
|
||||
database_setting_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'database_calendar_test.dart' as database_calendar_test;
|
||||
import 'database_filter_test.dart' as database_filter_test;
|
||||
import 'database_media_test.dart' as database_media_test;
|
||||
import 'database_row_cover_test.dart' as database_row_cover_test;
|
||||
import 'database_share_test.dart' as database_share_test;
|
||||
import 'database_sort_test.dart' as database_sort_test;
|
||||
import 'database_view_test.dart' as database_view_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
database_filter_test.main();
|
||||
database_sort_test.main();
|
||||
database_view_test.main();
|
||||
database_calendar_test.main();
|
||||
database_media_test.main();
|
||||
database_row_cover_test.main();
|
||||
database_share_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
import 'document_inline_page_reference_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Document deletion', () {
|
||||
testWidgets('Trash breadcrumb', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// This test shares behavior with the inline page reference test, thus
|
||||
// we utilize the same helper functions there.
|
||||
final name = await createDocumentToReference(tester);
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await triggerReferenceDocumentBySlashMenu(tester);
|
||||
|
||||
// Search for prefix of document
|
||||
await enterDocumentText(tester);
|
||||
|
||||
// Select result
|
||||
final optionFinder = find.descendant(
|
||||
of: find.byType(InlineActionsHandler),
|
||||
matching: find.text(name),
|
||||
);
|
||||
|
||||
await tester.tap(optionFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final mentionBlock = find.byType(MentionPageBlock);
|
||||
expect(mentionBlock, findsOneWidget);
|
||||
|
||||
// Delete the page
|
||||
await tester.hoverOnPageName(
|
||||
name,
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Navigate to the deleted page from the inline mention
|
||||
await tester.tap(mentionBlock);
|
||||
await tester.pumpUntilFound(find.byType(TrashBreadcrumb));
|
||||
|
||||
expect(find.byType(TrashBreadcrumb), findsOneWidget);
|
||||
|
||||
// Navigate using the trash breadcrumb
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(TrashBreadcrumb),
|
||||
matching: find.text(
|
||||
LocaleKeys.trash_text.tr(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.text(LocaleKeys.trash_restoreAll.tr()));
|
||||
|
||||
// Restore all
|
||||
await tester.tap(find.text(LocaleKeys.trash_restoreAll.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(LocaleKeys.trash_restore.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Navigate back to the document
|
||||
await tester.openPage('Getting started');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(mentionBlock);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(TrashBreadcrumb), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/keyboard.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
const _firstDocName = "Inline Sub Page Mention";
|
||||
const _createdPageName = "hi world";
|
||||
|
||||
// Test cases that are covered in this file:
|
||||
// - [x] Insert sub page mention from action menu (+)
|
||||
// - [x] Delete sub page mention from editor
|
||||
// - [x] Delete page from sidebar
|
||||
// - [x] Delete page from sidebar and then trash
|
||||
// - [x] Undo delete sub page mention
|
||||
// - [x] Cut+paste in same document
|
||||
// - [x] Cut+paste in different document
|
||||
// - [x] Cut+paste in same document and then paste again in same document
|
||||
// - [x] Turn paragraph with sub page mention into a heading
|
||||
// - [x] Turn heading with sub page mention into a paragraph
|
||||
// - [x] Duplicate a Block containing two sub page mentions
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document inline sub-page mention tests:', () {
|
||||
testWidgets('Insert (& delete) a sub page mention from action menu',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Delete from editor
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
// Undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Move to trash (delete from sidebar)
|
||||
await tester.rightClickOnPageName(_createdPageName);
|
||||
await tester.tapButtonWithName(ViewMoreActionType.delete.name);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(
|
||||
find.text(LocaleKeys.document_mention_trashHint.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// Delete from trash
|
||||
await tester.tapTrashButton();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text(LocaleKeys.trash_deleteAll.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text(LocaleKeys.button_delete.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.openPage(_firstDocName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
expect(
|
||||
find.text(LocaleKeys.document_mention_deletedPage.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Cut+paste in same document and cut+paste in different document',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Cut from editor
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
// Paste in same document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Cut again
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Create another document
|
||||
const anotherDocName = "Another Document";
|
||||
await tester.createOpenRenameDocumentUnderParent(
|
||||
name: anotherDocName,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Paste in document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(MentionSubPageBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: anotherDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
});
|
||||
testWidgets(
|
||||
'Cut+paste in same docuemnt and then paste again in same document',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Cut from editor
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNothing);
|
||||
expect(find.byType(MentionSubPageBlock), findsNothing);
|
||||
|
||||
// Paste in same document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Paste again
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
|
||||
expect(find.text('$_createdPageName (copy)'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('Turn into w/ sub page mentions', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: _firstDocName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
final headingText = LocaleKeys.document_slashMenu_name_heading1.tr();
|
||||
final paragraphText = LocaleKeys.document_slashMenu_name_text.tr();
|
||||
|
||||
// Turn into heading
|
||||
await tester.editor.openTurnIntoMenu([0]);
|
||||
await tester.tapButton(find.findTextInFlowyText(headingText));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
|
||||
// Turn into paragraph
|
||||
await tester.editor.openTurnIntoMenu([0]);
|
||||
await tester.tapButton(find.findTextInFlowyText(paragraphText));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsNWidgets(2));
|
||||
expect(find.byType(MentionSubPageBlock), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Duplicate a block containing two sub page mentions',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
|
||||
|
||||
await tester.insertInlineSubPageFromPlusMenu();
|
||||
|
||||
// Copy paste it
|
||||
await tester.editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [0], offset: 1),
|
||||
),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyC,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 1)),
|
||||
);
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
expect(find.text("$_createdPageName (copy)"), findsOneWidget);
|
||||
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
|
||||
|
||||
// Duplicate node from block action menu
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_duplicate.tr());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(_createdPageName), findsOneWidget);
|
||||
expect(find.text("$_createdPageName (copy)"), findsNWidgets(2));
|
||||
expect(find.text("$_createdPageName (copy) (copy)"), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension _InlineSubPageTestHelper on WidgetTester {
|
||||
Future<void> insertInlineSubPageFromPlusMenu() async {
|
||||
await editor.tapLineOfEditorAt(0);
|
||||
|
||||
await editor.showPlusMenu();
|
||||
|
||||
// Workaround to allow typing a document name
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
tester: this,
|
||||
withKeyUp: true,
|
||||
[
|
||||
LogicalKeyboardKey.keyH,
|
||||
LogicalKeyboardKey.keyI,
|
||||
LogicalKeyboardKey.space,
|
||||
LogicalKeyboardKey.keyW,
|
||||
LogicalKeyboardKey.keyO,
|
||||
LogicalKeyboardKey.keyR,
|
||||
LogicalKeyboardKey.keyL,
|
||||
LogicalKeyboardKey.keyD,
|
||||
],
|
||||
);
|
||||
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
tester: this,
|
||||
withKeyUp: true,
|
||||
[LogicalKeyboardKey.enter],
|
||||
);
|
||||
await pumpUntilFound(find.byType(MentionSubPageBlock));
|
||||
}
|
||||
}
|
||||
+139
-3
@@ -1,3 +1,8 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -7,9 +12,23 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// +, ... button beside the block component.
|
||||
group('document with option action button', () {
|
||||
testWidgets(
|
||||
'click + to add a block after current selection, and click + and option key to add a block before current selection',
|
||||
group('block option action:', () {
|
||||
Future<void> turnIntoBlock(
|
||||
WidgetTester tester,
|
||||
Path path, {
|
||||
required String menuText,
|
||||
required String afterType,
|
||||
}) async {
|
||||
await tester.editor.openTurnIntoMenu(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(menuText),
|
||||
);
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath(path);
|
||||
expect(node?.type, afterType);
|
||||
}
|
||||
|
||||
testWidgets('''click + to add a block after current selection,
|
||||
and click + and option key to add a block before current selection''',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -40,5 +59,122 @@ void main() {
|
||||
expect(editorState.getNodeAtPath([0])?.delta?.toPlainText(), isEmpty);
|
||||
expect(editorState.getNodeAtPath([2])?.delta?.toPlainText(), isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('turn into - single line', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const name = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: name);
|
||||
await tester.openPage(name);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('turn into');
|
||||
|
||||
// click the block option button to convert it to another blocks
|
||||
final values = {
|
||||
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_bulletedList.tr():
|
||||
BulletedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_numberedList.tr():
|
||||
NumberedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_todoList.tr():
|
||||
TodoListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
|
||||
};
|
||||
|
||||
for (final value in values.entries) {
|
||||
final menuText = value.key;
|
||||
final afterType = value.value;
|
||||
await turnIntoBlock(
|
||||
tester,
|
||||
[0],
|
||||
menuText: menuText,
|
||||
afterType: afterType,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('turn into - multi lines', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const name = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: name);
|
||||
await tester.openPage(name);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('turn into 1');
|
||||
await tester.ime.insertCharacter('\n');
|
||||
await tester.ime.insertText('turn into 2');
|
||||
|
||||
// click the block option button to convert it to another blocks
|
||||
final values = {
|
||||
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_bulletedList.tr():
|
||||
BulletedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_numberedList.tr():
|
||||
NumberedListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_todoList.tr():
|
||||
TodoListBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
|
||||
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
|
||||
};
|
||||
|
||||
for (final value in values.entries) {
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
editorState.selection = Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [1], offset: 2),
|
||||
);
|
||||
final menuText = value.key;
|
||||
final afterType = value.value;
|
||||
await turnIntoBlock(
|
||||
tester,
|
||||
[0],
|
||||
menuText: menuText,
|
||||
afterType: afterType,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'selecting the parent should deselect all the child nodes as well',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
const name = 'Test Document';
|
||||
await tester.createNewPageWithNameUnderParent(name: name);
|
||||
await tester.openPage(name);
|
||||
|
||||
// create a nested list
|
||||
// Item 1
|
||||
// Nested Item 1
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('Item 1');
|
||||
await tester.ime.insertCharacter('\n');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.ime.insertText('Nested Item 1');
|
||||
|
||||
// select the 'Nested Item 1' and then tap the option button of the 'Item 1'
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final selection = Selection.collapsed(
|
||||
Position(path: [0, 0], offset: 1),
|
||||
);
|
||||
editorState.selection = selection;
|
||||
await tester.pumpAndSettle();
|
||||
expect(editorState.selection, selection);
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
expect(editorState.selection, Selection.collapsed(Position(path: [0])));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
+87
-155
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
@@ -26,11 +27,16 @@ import '../../shared/util.dart';
|
||||
// - [x] Redo adding a SubPageBlock (Expect the view to be restored)
|
||||
// - [x] Redo delete of a SubPageBlock (Expect the view to be moved to trash again)
|
||||
// - [x] Renaming a child view (Expect the view name to be updated in the document)
|
||||
// - [x] Deleting a view (to trash) linked to a SubPageBlock shows a hint that the view is in trash (Expect a hint to be shown)
|
||||
// - [x] Deleting a view (in trash) linked to a SubPageBlock deletes the SubPageBlock (Expect the SubPageBlock to be deleted)
|
||||
// - [x] Deleting a view (to trash) linked to a SubPageBlock deleted the SubPageBlock (Expect the SubPageBlock to be deleted)
|
||||
// - [x] Duplicating a SubPageBlock node from Action Menu (Expect a new view is created under current view with same content and name + (copy))
|
||||
// - [x] Dragging a SubPageBlock node to a new position in the document (Expect everything to be normal)
|
||||
|
||||
/// The defaut page name is empty, if we're looking for a "text" we can look for
|
||||
/// [LocaleKeys.menuAppHeader_defaultNewPageName] but it won't work for eg. hoverOnPageName
|
||||
/// as it looks at the text provided instead of the actual displayed text.
|
||||
///
|
||||
const _defaultPageName = "";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -66,12 +72,9 @@ void main() {
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
@@ -94,11 +97,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionAddButton([0], false);
|
||||
@@ -151,11 +150,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionAddButton([0], false);
|
||||
@@ -213,11 +208,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor
|
||||
@@ -258,11 +249,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor
|
||||
@@ -300,39 +287,6 @@ void main() {
|
||||
expect(find.text('Child page (copy)'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Undo adding a SubPageBlock', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu(true);
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
// Undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Undo delete of a SubPageBlock', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -345,11 +299,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
@@ -359,6 +309,11 @@ void main() {
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
|
||||
// Since there is no selection active in editor before deleting Node,
|
||||
// we need to give focus back to the editor
|
||||
await tester.editor
|
||||
.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
@@ -370,55 +325,6 @@ void main() {
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
});
|
||||
|
||||
// Redo: undoing adding a subpage block, then redoing to bring it back
|
||||
// -> Add a subpage block
|
||||
// -> Undo
|
||||
// -> Redo
|
||||
testWidgets('Redo adding of a SubPageBlock', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
|
||||
await tester.insertSubPageFromSlashMenu(true);
|
||||
|
||||
await tester.expandOrCollapsePage(
|
||||
pageName: 'SubPageBlock',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
// Undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
|
||||
// Redo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isShiftPressed: true,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
});
|
||||
|
||||
// Redo: undoing deleting a subpage block, then redoing to delete it again
|
||||
// -> Add a subpage block
|
||||
// -> Delete
|
||||
@@ -436,11 +342,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
// Delete
|
||||
@@ -476,8 +378,7 @@ void main() {
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Delete a view first to trash, then from trash',
|
||||
(tester) async {
|
||||
testWidgets('Delete a view from sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
|
||||
@@ -489,38 +390,18 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
|
||||
final hintText = LocaleKeys.document_plugins_subPage_inTrashHint.tr();
|
||||
expect(find.text(hintText), findsNothing);
|
||||
|
||||
await tester.hoverOnPageName('Child page');
|
||||
await tester.tapDeletePageButton();
|
||||
await tester.hoverOnPageName(
|
||||
'Child page',
|
||||
onHover: () async {
|
||||
await tester.tapDeletePageButton();
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text(hintText), findsOne);
|
||||
|
||||
// Go to trash
|
||||
await tester.tapTrashButton();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on delete all button
|
||||
await tester.tap(find.text(LocaleKeys.trash_deleteAll.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap ok to delete app pages in trash
|
||||
await tester.tap(find.text(LocaleKeys.button_delete.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.openPage('SubPageBlock');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Child page'), findsNothing);
|
||||
expect(find.byType(SubPageBlockComponent), findsNothing);
|
||||
});
|
||||
@@ -537,11 +418,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester
|
||||
.hoverOnPageName(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
|
||||
await tester.renamePage('Child page');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
|
||||
expect(find.text('Child page'), findsNWidgets(2));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
@@ -578,10 +455,49 @@ void main() {
|
||||
expect(afterNode.type, SubPageBlockKeys.type);
|
||||
expect(afterNode.type, beforeNode.type);
|
||||
expect(find.byType(SubPageBlockComponent), findsOneWidget);
|
||||
expect(
|
||||
find.text(LocaleKeys.document_plugins_subPage_inTrashHint.tr()),
|
||||
findsNothing,
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -602,6 +518,22 @@ extension _SubPageTestHelper on WidgetTester {
|
||||
offset: 100,
|
||||
);
|
||||
|
||||
// Navigate to the previous page to see the SubPageBlock
|
||||
await openPage('SubPageBlock');
|
||||
await pumpAndSettle();
|
||||
|
||||
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,50 +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_create_and_delete_test.dart'
|
||||
as document_create_and_delete_test;
|
||||
import 'document_inline_page_reference_test.dart'
|
||||
as document_inline_page_reference_test;
|
||||
import 'document_more_actions_test.dart' as document_more_actions_test;
|
||||
import 'document_option_action_test.dart' as document_option_action_test;
|
||||
import 'document_shortcuts_test.dart' as document_shortcuts_test;
|
||||
import 'document_text_direction_test.dart' as document_text_direction_test;
|
||||
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
||||
import 'document_with_database_test.dart' as document_with_database_test;
|
||||
import 'document_with_file_test.dart' as document_with_file_test;
|
||||
import 'document_with_image_block_test.dart' as document_with_image_block_test;
|
||||
import 'document_with_inline_math_equation_test.dart'
|
||||
as document_with_inline_math_equation_test;
|
||||
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
||||
import 'document_with_multi_image_block_test.dart'
|
||||
as document_with_multi_image_block_test;
|
||||
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||
import 'edit_document_test.dart' as document_edit_test;
|
||||
|
||||
void startTesting() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
document_create_and_delete_test.main();
|
||||
document_edit_test.main();
|
||||
document_with_database_test.main();
|
||||
document_with_inline_page_test.main();
|
||||
document_with_inline_math_equation_test.main();
|
||||
document_with_cover_image_test.main();
|
||||
document_with_outline_block.main();
|
||||
document_with_toggle_list_test.main();
|
||||
document_copy_and_paste_test.main();
|
||||
document_codeblock_paste_test.main();
|
||||
document_alignment_test.main();
|
||||
document_text_direction_test.main();
|
||||
document_option_action_test.main();
|
||||
document_with_image_block_test.main();
|
||||
document_with_multi_image_block_test.main();
|
||||
document_inline_page_reference_test.main();
|
||||
document_more_actions_test.main();
|
||||
document_with_file_test.main();
|
||||
document_shortcuts_test.main();
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_create_and_delete_test.dart'
|
||||
as document_create_and_delete_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_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 'edit_document_test.dart' as document_edit_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
document_create_and_delete_test.main();
|
||||
document_edit_test.main();
|
||||
document_with_database_test.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.
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_app_lifecycle_test.dart' as document_app_lifecycle_test;
|
||||
import 'document_deletion_test.dart' as document_deletion_test;
|
||||
import 'document_inline_sub_page_test.dart' as document_inline_sub_page_test;
|
||||
import 'document_option_action_test.dart' as document_option_action_test;
|
||||
import 'document_title_test.dart' as document_title_test;
|
||||
import 'document_with_date_reminder_test.dart'
|
||||
as document_with_date_reminder_test;
|
||||
import 'document_with_toggle_heading_block_test.dart'
|
||||
as document_with_toggle_heading_block_test;
|
||||
import 'document_sub_page_test.dart' as document_sub_page_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
document_title_test.main();
|
||||
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();
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_alignment_test.dart' as document_alignment_test;
|
||||
import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
|
||||
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
||||
import 'document_text_direction_test.dart' as document_text_direction_test;
|
||||
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
document_with_outline_block.main();
|
||||
document_with_toggle_list_test.main();
|
||||
document_copy_and_paste_test.main();
|
||||
document_codeblock_paste_test.main();
|
||||
document_alignment_test.main();
|
||||
document_text_direction_test.main();
|
||||
|
||||
// Don't add new tests here.
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
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_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;
|
||||
|
||||
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();
|
||||
|
||||
// Disable subPage test temporarily, enable it in version 0.7.2
|
||||
// document_sub_page_test.main();
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -221,6 +222,85 @@ void main() {
|
||||
expect(newTitle, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('execute undo and redo in title', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
// press a random key to make the undo stack not empty
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.keyA);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// undo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
// wait for the undo to be applied
|
||||
await tester.pumpAndSettle(Durations.long1);
|
||||
|
||||
// expect the title is empty
|
||||
expect(
|
||||
tester
|
||||
.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(''),
|
||||
)
|
||||
.controller
|
||||
?.text,
|
||||
'',
|
||||
);
|
||||
|
||||
// redo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
if (UniversalPlatform.isMacOS) {
|
||||
expect(
|
||||
tester
|
||||
.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
)
|
||||
.controller
|
||||
?.text,
|
||||
_testDocumentName,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('escape key should exit the editing mode', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
tester
|
||||
.widget<TextField>(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
)
|
||||
.focusNode
|
||||
?.hasFocus,
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('press arrow down key in title, check if the cursor flashes',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
@@ -248,5 +328,24 @@ void main() {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'hover on the cover title, check if the add icon & add cover button are shown',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.hoverOnWidget(
|
||||
title,
|
||||
onHover: () async {
|
||||
expect(find.byType(DocumentCoverWidget), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
});
|
||||
}
|
||||
+2
-3
@@ -1,9 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -14,7 +13,7 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('cover image', () {
|
||||
group('cover image:', () {
|
||||
testWidgets('document cover tests', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/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';
|
||||
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() {
|
||||
setUp(() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('date or reminder block in document', () {
|
||||
testWidgets("insert date with time block", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'Date with time test',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
|
||||
);
|
||||
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
dateFormat: UserDateFormatPB.Friendly,
|
||||
timeFormat: UserTimeFormatPB.TwentyFourHour,
|
||||
);
|
||||
final DateTime currentDateTime = DateTime.now();
|
||||
final String formattedDate =
|
||||
dateTimeSettings.dateFormat.formatDate(currentDateTime, false);
|
||||
|
||||
// get current date in editor
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
|
||||
// tap on date field
|
||||
await tester.tap(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// tap the toggle of include time
|
||||
await tester.tap(find.byType(Toggle));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// add time 11:12
|
||||
final textField = find
|
||||
.descendant(
|
||||
of: find.byType(DesktopAppFlowyDatePicker),
|
||||
matching: find.byType(TextField),
|
||||
)
|
||||
.last;
|
||||
await tester.pumpUntilFound(textField);
|
||||
await tester.enterText(textField, "11:12");
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// we will get field with current date and 11:12 as time
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate 11:12'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("insert date with reminder block", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'Date with reminder test',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
|
||||
);
|
||||
|
||||
final dateTimeSettings = DateTimeSettingsPB(
|
||||
dateFormat: UserDateFormatPB.Friendly,
|
||||
timeFormat: UserTimeFormatPB.TwentyFourHour,
|
||||
);
|
||||
final DateTime currentDateTime = DateTime.now();
|
||||
final String formattedDate =
|
||||
dateTimeSettings.dateFormat.formatDate(currentDateTime, false);
|
||||
|
||||
// get current date in editor
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
|
||||
// tap on date field
|
||||
await tester.tap(find.byType(MentionDateBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// tap reminder and set reminder to 1 day before
|
||||
await tester.tap(find.text(LocaleKeys.datePicker_reminderLabel.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.textContaining(
|
||||
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// we will get field with current date reminder_clock.svg icon
|
||||
expect(find.byType(MentionDateBlock), findsOneWidget);
|
||||
expect(find.text('@$formattedDate'), findsOneWidget);
|
||||
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
+1
-3
@@ -47,9 +47,7 @@ void main() {
|
||||
mockPickFilePaths(paths: [filePath]);
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
await tester.tap(
|
||||
find.text(LocaleKeys.document_plugins_file_fileUploadHint.tr()),
|
||||
);
|
||||
await tester.tapFileUploadHint();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FileUploadMenu), findsNothing);
|
||||
|
||||
+1
-5
@@ -6,7 +6,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
@@ -160,10 +159,7 @@ void main() {
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
await tester.tapButtonWithName('Unsplash');
|
||||
await tester.pumpUntilFound(find.byType(UnsplashImageWidget));
|
||||
expect(find.byType(UnsplashImageWidget), findsOneWidget);
|
||||
expect(find.text('Unsplash'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/shared/flowy_error_page.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -92,7 +92,7 @@ void main() {
|
||||
);
|
||||
expect(finder, findsOneWidget);
|
||||
await tester.tapButton(finder);
|
||||
expect(find.byType(AppFlowyErrorPage), findsOneWidget);
|
||||
expect(find.byType(GridPage), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
@@ -26,7 +25,6 @@ import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
|
||||
+3
-11
@@ -1,9 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -172,7 +169,6 @@ Future<void> insertOutlineInDocument(WidgetTester tester) async {
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_outline.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
@@ -182,13 +178,9 @@ Future<void> hoverAndClickDepthOptionAction(
|
||||
List<int> path,
|
||||
int level,
|
||||
) async {
|
||||
await tester.editor.hoverAndClickOptionMenuButton([3]);
|
||||
await tester.tap(find.byType(AppFlowyPopover).hitTestable().last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find a total of 4 HoverButtons under the [BlockOptionButton],
|
||||
// in addition to 3 HoverButtons under the [DepthOptionAction] - (child of BlockOptionButton)
|
||||
await tester.tap(find.byType(HoverButton).hitTestable().at(3 + level));
|
||||
await tester.editor.openDepthMenu(path);
|
||||
final type = OptionDepthType.fromLevel(level);
|
||||
await tester.tapButton(find.findTextInFlowyText(type.description));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
const String _heading1 = 'Heading 1';
|
||||
const String _heading2 = 'Heading 2';
|
||||
const String _heading3 = 'Heading 3';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('toggle heading block test:', () {
|
||||
testWidgets('insert toggle heading 1 - 3 block', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'toggle heading block test',
|
||||
);
|
||||
|
||||
for (var i = 1; i <= 3; i++) {
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await _insertToggleHeadingBlockInDocument(tester, i);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is ToggleListBlockComponentWidget &&
|
||||
widget.node.attributes[ToggleListBlockKeys.level] == i,
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('insert toggle heading 1 - 3 block by shortcuts',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'toggle heading block test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('# > $_heading1\n');
|
||||
await tester.ime.insertText('## > $_heading2\n');
|
||||
await tester.ime.insertText('### > $_heading3\n');
|
||||
await tester.ime.insertText('> # $_heading1\n');
|
||||
await tester.ime.insertText('> ## $_heading2\n');
|
||||
await tester.ime.insertText('> ### $_heading3\n');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(ToggleListBlockComponentWidget),
|
||||
findsNWidgets(6),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert toggle heading and convert it to heading',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'toggle heading block test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('# > $_heading1\n');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.ime.insertText('item 1');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [0], offset: _heading1.length),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapButton(find.byType(HeadingPopup));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(HeadingButton),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
|
||||
// tap the H1 button
|
||||
await tester.tapButton(find.byType(HeadingButton).at(0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node1 = editorState.document.nodeAtPath([0])!;
|
||||
expect(node1.type, HeadingBlockKeys.type);
|
||||
expect(node1.attributes[HeadingBlockKeys.level], 1);
|
||||
|
||||
final node2 = editorState.document.nodeAtPath([1])!;
|
||||
expect(node2.type, ParagraphBlockKeys.type);
|
||||
expect(node2.delta!.toPlainText(), 'item 1');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _insertToggleHeadingBlockInDocument(
|
||||
WidgetTester tester,
|
||||
int level,
|
||||
) async {
|
||||
final name = switch (level) {
|
||||
1 => LocaleKeys.document_slashMenu_name_toggleHeading1.tr(),
|
||||
2 => LocaleKeys.document_slashMenu_name_toggleHeading2.tr(),
|
||||
3 => LocaleKeys.document_slashMenu_name_toggleHeading3.tr(),
|
||||
_ => throw Exception('Invalid level: $level'),
|
||||
};
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
name,
|
||||
offset: 150,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
+49
@@ -214,5 +214,54 @@ void main() {
|
||||
|
||||
expectToggleListOpened();
|
||||
});
|
||||
|
||||
Future<void> prepareToggleHeadingBlock(
|
||||
WidgetTester tester,
|
||||
String text,
|
||||
) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText(text);
|
||||
}
|
||||
|
||||
testWidgets('> + # to toggle heading 1 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '> # Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 1);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
|
||||
testWidgets('> + ### to toggle heading 3 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '> ### Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 3);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
|
||||
testWidgets('# + > to toggle heading 1 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '# > Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 1);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
|
||||
testWidgets('### + > to toggle heading 3 block', (tester) async {
|
||||
await prepareToggleHeadingBlock(tester, '### > Hello');
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ToggleListBlockKeys.type);
|
||||
expect(node.attributes[ToggleListBlockKeys.level], 3);
|
||||
expect(node.delta!.toPlainText(), 'Hello');
|
||||
});
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ void main() {
|
||||
testWidgets('empty test', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.wait(1000);
|
||||
await tester.wait(500);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -189,15 +189,46 @@ void main() {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.hoverOnFirstRowOfGrid(() async {
|
||||
// Open the row menu and then click the delete
|
||||
// Open the row menu and click the delete button
|
||||
await tester.tapRowMenuButtonInGrid();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapDeleteOnRowMenu();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 3 initial rows - 1 deleted
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
expect(find.byType(ConfirmPopup), findsOneWidget);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
|
||||
testWidgets('delete row in two views', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.renameLinkedView(
|
||||
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Grid),
|
||||
'grid 1',
|
||||
);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
|
||||
await tester.renameLinkedView(
|
||||
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Grid).at(1),
|
||||
'grid 2',
|
||||
);
|
||||
tester.assertNumberOfRowsInGridPage(3);
|
||||
|
||||
await tester.hoverOnFirstRowOfGrid(() async {
|
||||
// Open the row menu and click the delete button
|
||||
await tester.tapRowMenuButtonInGrid();
|
||||
await tester.tapDeleteOnRowMenu();
|
||||
});
|
||||
expect(find.byType(ConfirmPopup), findsOneWidget);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
// 3 initial rows - 1 deleted
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
|
||||
await tester.tapTabBarLinkedViewByViewName('grid 1');
|
||||
tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'grid_edit_row_test.dart' as grid_edit_row_test_runner;
|
||||
import 'grid_filter_and_sort_test.dart' as grid_filter_and_sort_test_runner;
|
||||
import 'grid_reopen_test.dart' as grid_reopen_test_runner;
|
||||
import 'grid_reorder_row_test.dart' as grid_reorder_row_test_runner;
|
||||
import 'grid_row_test.dart' as grid_row_test_runner;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
grid_reopen_test_runner.main();
|
||||
grid_row_test_runner.main();
|
||||
grid_reorder_row_test_runner.main();
|
||||
grid_filter_and_sort_test_runner.main();
|
||||
grid_edit_row_test_runner.main();
|
||||
// grid_calculations_test_runner.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
-1
@@ -10,7 +10,6 @@ import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/keyboard.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -11,7 +11,7 @@ void main() {
|
||||
|
||||
const emoji = '😁';
|
||||
|
||||
group('Icon', () {
|
||||
group('Icon:', () {
|
||||
testWidgets('Update page icon in sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -52,6 +52,7 @@ void main() {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
|
||||
@@ -198,7 +198,7 @@ void main() {
|
||||
layout: ViewLayoutPB.Grid,
|
||||
onHover: () async {
|
||||
expect(find.byType(ViewAddButton), findsNothing);
|
||||
expect(find.byType(ViewMoreActionButton), findsOneWidget);
|
||||
expect(find.byType(ViewMoreActionPopover), findsOneWidget);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
|
||||
import 'sidebar_icon_test.dart' as sidebar_icon_test;
|
||||
import 'sidebar_test.dart' as sidebar_test;
|
||||
import 'sidebar_view_item_test.dart' as sidebar_view_item_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -12,4 +13,5 @@ void main() {
|
||||
// sidebar_expanded_test.main();
|
||||
sidebar_favorite_test.main();
|
||||
sidebar_icon_test.main();
|
||||
sidebar_view_item_test.main();
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Sidebar view item tests', () {
|
||||
testWidgets('Access view item context menu by right click', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// Right click on the view item and change icon
|
||||
await tester.hoverOnWidget(
|
||||
find.byType(ViewItem),
|
||||
onHover: () async {
|
||||
await tester.tap(find.byType(ViewItem), buttons: kSecondaryButton);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
|
||||
// Change icon
|
||||
final changeIconButton =
|
||||
find.text(LocaleKeys.document_plugins_cover_changeIcon.tr());
|
||||
|
||||
await tester.tapButton(changeIconButton);
|
||||
await tester.pumpUntilFound(find.byType(FlowyEmojiPicker));
|
||||
|
||||
const emoji = '😁';
|
||||
await tester.tapEmoji(emoji);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tester.expectViewHasIcon(gettingStarted, ViewLayoutPB.Document, emoji);
|
||||
});
|
||||
});
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
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 'tabs_test.dart' as tabs_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();
|
||||
tabs_test.main();
|
||||
zoom_in_out_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner.dart' as document_test_runner;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/document/document_test_runner_1.dart' as document_test_runner_1;
|
||||
import 'desktop/uncategorized/switch_folder_test.dart' as switch_folder_test;
|
||||
|
||||
Future<void> main() async {
|
||||
@@ -11,11 +10,7 @@ Future<void> main() async {
|
||||
Future<void> runIntegration1OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
first_test.main();
|
||||
|
||||
switch_folder_test.main();
|
||||
document_test_runner.startTesting();
|
||||
|
||||
// DON'T add more tests here. This is the first test runner for desktop.
|
||||
document_test_runner_1.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -1,18 +1,7 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/database/database_calendar_test.dart' as database_calendar_test;
|
||||
import 'desktop/database/database_cell_test.dart' as database_cell_test;
|
||||
import 'desktop/database/database_field_settings_test.dart'
|
||||
as database_field_settings_test;
|
||||
import 'desktop/database/database_field_test.dart' as database_field_test;
|
||||
import 'desktop/database/database_filter_test.dart' as database_filter_test;
|
||||
import 'desktop/database/database_media_test.dart' as database_media_test;
|
||||
import 'desktop/database/database_row_page_test.dart' as database_row_page_test;
|
||||
import 'desktop/database/database_setting_test.dart' as database_setting_test;
|
||||
import 'desktop/database/database_share_test.dart' as database_share_test;
|
||||
import 'desktop/database/database_sort_test.dart' as database_sort_test;
|
||||
import 'desktop/database/database_view_test.dart' as database_view_test;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/database/database_test_runner_1.dart' as database_test_runner_1;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration2OnDesktop();
|
||||
@@ -21,20 +10,8 @@ Future<void> main() async {
|
||||
Future<void> runIntegration2OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
first_test.main();
|
||||
|
||||
database_cell_test.main();
|
||||
database_field_test.main();
|
||||
database_field_settings_test.main();
|
||||
database_share_test.main();
|
||||
database_row_page_test.main();
|
||||
database_setting_test.main();
|
||||
database_filter_test.main();
|
||||
database_sort_test.main();
|
||||
database_view_test.main();
|
||||
database_calendar_test.main();
|
||||
database_media_test.main();
|
||||
|
||||
database_test_runner_1.main();
|
||||
// DON'T add more tests here. This is the second test runner for desktop.
|
||||
}
|
||||
@@ -1,28 +1,8 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/board/board_test_runner.dart' as board_test_runner;
|
||||
import 'desktop/database/database_row_cover_test.dart'
|
||||
as database_row_cover_test;
|
||||
import 'desktop/document/document_title_test.dart' as document_title_test;
|
||||
import 'desktop/document/document_app_lifecycle_test.dart'
|
||||
as document_app_lifecycle_test;
|
||||
import 'desktop/document/document_sub_page_test.dart' as document_sub_page_test;
|
||||
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_edit_row_test.dart' as grid_edit_row_test_runner;
|
||||
import 'desktop/grid/grid_row_test.dart' as grid_create_row_test_runner;
|
||||
import 'desktop/settings/settings_runner.dart' as settings_test_runner;
|
||||
import 'desktop/sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||
import 'desktop/uncategorized/emoji_shortcut_test.dart' as emoji_shortcut_test;
|
||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||
import 'desktop/uncategorized/hotkeys_test.dart' as hotkeys_test;
|
||||
import 'desktop/uncategorized/import_files_test.dart' as import_files_test;
|
||||
import 'desktop/uncategorized/share_markdown_test.dart' as share_markdown_test;
|
||||
import 'desktop/uncategorized/tabs_test.dart' as tabs_test;
|
||||
import 'desktop/uncategorized/zoom_in_out_test.dart' as zoom_in_out_test;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
import 'desktop/grid/grid_test_runner_1.dart' as grid_test_runner_1;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration3OnDesktop();
|
||||
@@ -31,27 +11,9 @@ Future<void> main() async {
|
||||
Future<void> runIntegration3OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// This test must be run first, otherwise the CI will fail.
|
||||
first_test.main();
|
||||
|
||||
hotkeys_test.main();
|
||||
emoji_shortcut_test.main();
|
||||
hotkeys_test.main();
|
||||
emoji_shortcut_test.main();
|
||||
settings_test_runner.main();
|
||||
share_markdown_test.main();
|
||||
import_files_test.main();
|
||||
sidebar_test_runner.main();
|
||||
board_test_runner.main();
|
||||
tabs_test.main();
|
||||
database_row_cover_test.main();
|
||||
grid_reopen_test_runner.main();
|
||||
grid_create_row_test_runner.main();
|
||||
grid_reorder_row_test_runner.main();
|
||||
grid_filter_and_sort_test_runner.main();
|
||||
grid_edit_row_test_runner.main();
|
||||
zoom_in_out_test.main();
|
||||
document_title_test.main();
|
||||
document_sub_page_test.main();
|
||||
document_app_lifecycle_test.main();
|
||||
grid_test_runner_1.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner_2.dart' as document_test_runner_2;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration4OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration4OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
document_test_runner_2.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/database/database_test_runner_2.dart' as database_test_runner_2;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration5OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration5OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
database_test_runner_2.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
import 'desktop/settings/settings_runner.dart' as settings_test_runner;
|
||||
import 'desktop/sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||
import 'desktop/uncategorized/uncategorized_test_runner_1.dart'
|
||||
as uncategorized_test_runner_1;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration6OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration6OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
settings_test_runner.main();
|
||||
sidebar_test_runner.main();
|
||||
uncategorized_test_runner_1.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner_3.dart' as document_test_runner_3;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration7OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration7OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
document_test_runner_3.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'desktop/document/document_test_runner_4.dart' as document_test_runner_4;
|
||||
import 'desktop/first_test/first_test.dart' as first_test;
|
||||
|
||||
Future<void> main() async {
|
||||
await runIntegration8OnDesktop();
|
||||
}
|
||||
|
||||
Future<void> runIntegration8OnDesktop() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
first_test.main();
|
||||
|
||||
document_test_runner_4.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'document/publish_test.dart' as publish_test;
|
||||
import 'document/share_link_test.dart' as share_link_test;
|
||||
import 'workspace/workspace_operations_test.dart' as workspace_operations_test;
|
||||
|
||||
Future<void> main() async {
|
||||
workspace_operations_test.main();
|
||||
share_link_test.main();
|
||||
publish_test.main();
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('publish:', () {
|
||||
testWidgets('publish document', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
await tester.editor.openMoreActionMenuOnMobile();
|
||||
|
||||
// click the publish button
|
||||
await tester.editor.clickMoreActionItemOnMobile(
|
||||
LocaleKeys.shareAction_publish.tr(),
|
||||
);
|
||||
|
||||
// wait the notification dismiss
|
||||
final publishSuccessText = find.findTextInFlowyText(
|
||||
LocaleKeys.publish_publishSuccessfully.tr(),
|
||||
);
|
||||
expect(publishSuccessText, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(publishSuccessText);
|
||||
|
||||
// open the menu again, to check the publish status
|
||||
await tester.editor.openMoreActionMenuOnMobile();
|
||||
// expect to see the unpublish button and the visit site button
|
||||
expect(
|
||||
find.text(LocaleKeys.shareAction_unPublish.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.text(LocaleKeys.shareAction_visitSite.tr()),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// unpublish the document
|
||||
await tester.editor.clickMoreActionItemOnMobile(
|
||||
LocaleKeys.shareAction_unPublish.tr(),
|
||||
);
|
||||
final unPublishSuccessText = find.findTextInFlowyText(
|
||||
LocaleKeys.publish_unpublishSuccessfully.tr(),
|
||||
);
|
||||
expect(unPublishSuccessText, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(unPublishSuccessText);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('share link:', () {
|
||||
testWidgets('copy share link and paste it on doc', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open the getting started page and paste the link
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// open the more action menu
|
||||
await tester.editor.openMoreActionMenuOnMobile();
|
||||
|
||||
// click the share link item
|
||||
await tester.editor.clickMoreActionItemOnMobile(
|
||||
LocaleKeys.shareAction_copyLink.tr(),
|
||||
);
|
||||
|
||||
// check the clipboard
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text,
|
||||
matches(appflowySharePageLinkPattern),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'page_style_test.dart' as page_style_test;
|
||||
import 'title_test.dart' as title_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
title_test.main();
|
||||
page_style_test.main();
|
||||
}
|
||||
@@ -57,10 +57,11 @@ void main() {
|
||||
.single
|
||||
.widget as AppFlowyEditorPage;
|
||||
return editorPage.styleCustomizer
|
||||
.style()
|
||||
.textStyleConfiguration
|
||||
.text
|
||||
.height!;
|
||||
.style()
|
||||
.textStyleConfiguration
|
||||
.text
|
||||
.height ??
|
||||
PageStyleLineHeightLayout.normal.lineHeight;
|
||||
}
|
||||
|
||||
testWidgets('change font size in page style settings', (tester) async {
|
||||
@@ -126,7 +127,6 @@ void main() {
|
||||
|
||||
// click done button to exit the page style settings
|
||||
await tester.tapButton(find.byType(BottomSheetDoneButton).first);
|
||||
await tester.tapButton(find.byType(BottomSheetDoneButton).first);
|
||||
|
||||
// check the cover
|
||||
final builtInCover = find.descendant(
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document title:', () {
|
||||
testWidgets('create a new page, the title should be empty', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
|
||||
final createPageButton = find.byKey(
|
||||
BottomNavigationBarItemType.add.valueKey,
|
||||
);
|
||||
await tester.tapButton(createPageButton);
|
||||
expect(find.byType(MobileDocumentScreen), findsOneWidget);
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
expect(title, findsOneWidget);
|
||||
final textField = tester.widget<TextField>(title);
|
||||
expect(textField.focusNode!.hasFocus, isTrue);
|
||||
|
||||
// input new name and press done button
|
||||
const name = 'test document';
|
||||
await tester.enterText(title, name);
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
final newTitle = tester.editor.findDocumentTitle(name);
|
||||
expect(newTitle, findsOneWidget);
|
||||
expect(textField.controller!.text, name);
|
||||
|
||||
// the document should get focus
|
||||
final editor = tester.widget<AppFlowyEditorPage>(
|
||||
find.byType(AppFlowyEditorPage),
|
||||
);
|
||||
expect(
|
||||
editor.editorState.selection,
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+7
-7
@@ -3,10 +3,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/editor/mobile_editor_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/document_page.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
@@ -34,13 +35,12 @@ void main() {
|
||||
cloudType: AuthenticatorType.local,
|
||||
);
|
||||
|
||||
// click the anonymousSignInButton
|
||||
final anonymousSignInButton = find.byType(SignInAnonymousButtonV2);
|
||||
expect(anonymousSignInButton, findsOneWidget);
|
||||
await tester.tapButton(anonymousSignInButton);
|
||||
|
||||
// tap the create page button
|
||||
final createPageButton = find.byKey(mobileCreateNewPageButtonKey);
|
||||
final createPageButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowySvg &&
|
||||
widget.svg.path == FlowySvgs.m_home_unselected_m.path,
|
||||
);
|
||||
await tester.tapButton(createPageButton);
|
||||
expect(find.byType(MobileDocumentScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@@ -29,11 +29,6 @@ void main() {
|
||||
testWidgets('anon user and then sign in', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
|
||||
// click the anonymousSignInButton
|
||||
final anonymousSignInButton = find.byType(SignInAnonymousButtonV2);
|
||||
expect(anonymousSignInButton, findsOneWidget);
|
||||
await tester.tapButton(anonymousSignInButton);
|
||||
|
||||
// expect to see the home page
|
||||
expect(find.byType(MobileHomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'mobile/document/page_style_test.dart' as page_style_test;
|
||||
@@ -5,7 +6,9 @@ import 'mobile/home_page/create_new_page_test.dart' as create_new_page_test;
|
||||
import 'mobile/sign_in/anonymous_sign_in_test.dart' as anonymous_sign_in_test;
|
||||
|
||||
Future<void> runIntegrationOnMobile() async {
|
||||
Log.shared.disableLog = true;
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
anonymous_sign_in_test.main();
|
||||
create_new_page_test.main();
|
||||
page_style_test.main();
|
||||
|
||||
@@ -3,6 +3,11 @@ import 'dart:io';
|
||||
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 'mobile_runner.dart';
|
||||
|
||||
/// The main task runner for all integration tests in AppFlowy.
|
||||
@@ -17,6 +22,11 @@ Future<void> main() async {
|
||||
await runIntegration1OnDesktop();
|
||||
await runIntegration2OnDesktop();
|
||||
await runIntegration3OnDesktop();
|
||||
await runIntegration4OnDesktop();
|
||||
await runIntegration5OnDesktop();
|
||||
await runIntegration6OnDesktop();
|
||||
await runIntegration7OnDesktop();
|
||||
await runIntegration8OnDesktop();
|
||||
} else if (Platform.isIOS || Platform.isAndroid) {
|
||||
await runIntegrationOnMobile();
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class FlowyTestContext {
|
||||
FlowyTestContext({required this.applicationDataDirectory});
|
||||
@@ -105,7 +106,7 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> waitUntilSignInPageShow() async {
|
||||
if (isAuthEnabled) {
|
||||
if (isAuthEnabled || UniversalPlatform.isMobile) {
|
||||
final finder = find.byType(SignInAnonymousButtonV2);
|
||||
await pumpUntilFound(finder, timeout: const Duration(seconds: 30));
|
||||
expect(finder, findsOneWidget);
|
||||
@@ -158,17 +159,12 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
|
||||
Future<void> tapButton(
|
||||
Finder finder, {
|
||||
int? pointer,
|
||||
int buttons = kPrimaryButton,
|
||||
bool warnIfMissed = false,
|
||||
int milliseconds = 500,
|
||||
bool pumpAndSettle = true,
|
||||
}) async {
|
||||
await tap(
|
||||
finder,
|
||||
buttons: buttons,
|
||||
warnIfMissed: warnIfMissed,
|
||||
);
|
||||
await tap(finder, buttons: buttons, warnIfMissed: warnIfMissed);
|
||||
|
||||
if (pumpAndSettle) {
|
||||
await this.pumpAndSettle(
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
@@ -14,13 +8,16 @@ import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_button.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_new_page_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||
@@ -40,6 +37,10 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'emoji.dart';
|
||||
@@ -185,6 +186,21 @@ extension CommonOperations on WidgetTester {
|
||||
}
|
||||
}
|
||||
|
||||
/// Right click on the page name.
|
||||
Future<void> rightClickOnPageName(
|
||||
String name, {
|
||||
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||
}) async {
|
||||
final page = findPageName(name, layout: layout);
|
||||
await hoverOnPageName(
|
||||
name,
|
||||
onHover: () async {
|
||||
await tap(page, buttons: kSecondaryMouseButton);
|
||||
await pumpAndSettle();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// open the page with given name.
|
||||
Future<void> openPage(
|
||||
String name, {
|
||||
@@ -199,7 +215,10 @@ extension CommonOperations on WidgetTester {
|
||||
///
|
||||
/// Must call [hoverOnPageName] first.
|
||||
Future<void> tapPageOptionButton() async {
|
||||
final optionButton = find.byType(ViewMoreActionButton);
|
||||
final optionButton = find.descendant(
|
||||
of: find.byType(ViewMoreActionPopover),
|
||||
matching: find.byFlowySvg(FlowySvgs.workspace_three_dots_s),
|
||||
);
|
||||
await tapButton(optionButton);
|
||||
}
|
||||
|
||||
@@ -279,12 +298,14 @@ extension CommonOperations on WidgetTester {
|
||||
|
||||
/// Tap the delete permanently button.
|
||||
///
|
||||
/// the restore button will show after the current page is deleted.
|
||||
/// the delete permanently button will show after the current page is deleted.
|
||||
Future<void> tapDeletePermanentlyButton() async {
|
||||
final restoreButton = find.textContaining(
|
||||
final deleteButton = find.textContaining(
|
||||
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
||||
);
|
||||
await tapButton(restoreButton);
|
||||
await tapButton(deleteButton);
|
||||
await tap(find.text(LocaleKeys.button_delete.tr()));
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Tap the share button above the document page.
|
||||
@@ -295,6 +316,15 @@ 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.
|
||||
@@ -324,14 +354,10 @@ extension CommonOperations on WidgetTester {
|
||||
}
|
||||
await pumpAndSettle();
|
||||
|
||||
final defaultPageName = layout == ViewLayoutPB.Document
|
||||
? '' // the document name is empty by default
|
||||
: LocaleKeys.menuAppHeader_defaultNewPageName.tr();
|
||||
|
||||
// hover on it and change it's name
|
||||
if (name != null) {
|
||||
await hoverOnPageName(
|
||||
defaultPageName,
|
||||
layout.defaultName,
|
||||
layout: layout,
|
||||
onHover: () async {
|
||||
await renamePage(name);
|
||||
@@ -345,13 +371,39 @@ extension CommonOperations on WidgetTester {
|
||||
if (openAfterCreated) {
|
||||
await openPage(
|
||||
// if the name is null, use the default name
|
||||
name ?? defaultPageName,
|
||||
name ?? layout.defaultName,
|
||||
layout: layout,
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createOpenRenameDocumentUnderParent({
|
||||
required String name,
|
||||
String? parentName,
|
||||
}) async {
|
||||
// create a new page
|
||||
await tapAddViewButton(name: parentName ?? gettingStarted);
|
||||
await tapButtonWithName(ViewLayoutPB.Document.menuName);
|
||||
final settingsOrFailure = await getIt<KeyValueStorage>().getWithFormat(
|
||||
KVKeys.showRenameDialogWhenCreatingNewFile,
|
||||
(value) => bool.parse(value),
|
||||
);
|
||||
final showRenameDialog = settingsOrFailure ?? false;
|
||||
if (showRenameDialog) {
|
||||
await tapOKButton();
|
||||
}
|
||||
await pumpAndSettle();
|
||||
|
||||
// open the page after created
|
||||
await openPage(ViewLayoutPB.Document.defaultName);
|
||||
await pumpAndSettle();
|
||||
|
||||
// Enter new name in the document title
|
||||
await enterText(find.byType(TextFieldWithMetricLines), name);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Create a new page in the space
|
||||
Future<void> createNewPageInSpace({
|
||||
required String spaceName,
|
||||
@@ -723,6 +775,18 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsFinder on CommonFinders {
|
||||
@@ -751,6 +815,25 @@ extension SettingsFinder on CommonFinders {
|
||||
.first;
|
||||
}
|
||||
|
||||
extension FlowySvgFinder on CommonFinders {
|
||||
Finder byFlowySvg(FlowySvgData svg) => _FlowySvgFinder(svg);
|
||||
}
|
||||
|
||||
class _FlowySvgFinder extends MatchFinder {
|
||||
_FlowySvgFinder(this.svg);
|
||||
|
||||
final FlowySvgData svg;
|
||||
|
||||
@override
|
||||
String get description => 'flowy_svg "$svg"';
|
||||
|
||||
@override
|
||||
bool matches(Element candidate) {
|
||||
final Widget widget = candidate.widget;
|
||||
return widget is FlowySvg && widget.svg == svg;
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewLayoutPBTest on ViewLayoutPB {
|
||||
String get menuName {
|
||||
switch (this) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@@ -53,7 +55,7 @@ import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/ti
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/url.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/date_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/date_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/media_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_cell_editor.dart';
|
||||
@@ -411,8 +413,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> selectOption({required String name}) async {
|
||||
final option = find.byWidgetPredicate(
|
||||
(widget) => widget is SelectOptionTagCell && widget.option.name == name,
|
||||
final option = find.descendant(
|
||||
of: find.byType(SelectOptionCellEditor),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) => widget is SelectOptionTagCell && widget.option.name == name,
|
||||
),
|
||||
);
|
||||
|
||||
await tapButton(option);
|
||||
@@ -504,6 +509,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> renameChecklistTask({
|
||||
required int index,
|
||||
required String name,
|
||||
bool enter = true,
|
||||
}) async {
|
||||
final textField = find
|
||||
.descendant(
|
||||
@@ -513,7 +519,9 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
.at(index);
|
||||
|
||||
await enterText(textField, name);
|
||||
await testTextInput.receiveAction(TextInputAction.done);
|
||||
if (enter) {
|
||||
await testTextInput.receiveAction(TextInputAction.done);
|
||||
}
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
@@ -531,14 +539,32 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> deleteChecklistTask({required int index}) async {
|
||||
final task = find.byType(ChecklistItem).at(index);
|
||||
|
||||
await startGesture(getCenter(task), kind: PointerDeviceKind.mouse);
|
||||
await pumpAndSettle();
|
||||
|
||||
final button = find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
|
||||
await hoverOnWidget(
|
||||
task,
|
||||
onHover: () async {
|
||||
final button = find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
|
||||
);
|
||||
await tapButton(button);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tapButton(button);
|
||||
void assertPhantomChecklistItemAtIndex({required int index}) {
|
||||
final paddings = find.descendant(
|
||||
of: find.descendant(
|
||||
of: find.byType(ChecklistRowDetailCell),
|
||||
matching: find.byType(ReorderableListView),
|
||||
),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is Padding &&
|
||||
(widget.child is ChecklistItem ||
|
||||
widget.child is PhantomChecklistItem),
|
||||
),
|
||||
);
|
||||
final phantom = widget<Padding>(paddings.at(index)).child!;
|
||||
expect(phantom is PhantomChecklistItem, true);
|
||||
}
|
||||
|
||||
Future<void> openFirstRowDetailPage() async {
|
||||
@@ -570,7 +596,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
final banner = find.byType(RowBanner);
|
||||
expect(banner, findsOneWidget);
|
||||
|
||||
await startGesture(getCenter(banner), kind: PointerDeviceKind.mouse);
|
||||
await startGesture(
|
||||
getCenter(banner) + const Offset(0, -10),
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
@@ -582,7 +611,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButtonWithName(
|
||||
LocaleKeys.document_plugins_cover_addCover.tr(),
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> openEmojiPicker() async =>
|
||||
@@ -1157,10 +1185,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(find.byType(ChecklistFilterConditionList));
|
||||
}
|
||||
|
||||
Future<void> tapDateFilterButtonInGrid() async {
|
||||
await tapButton(find.byType(DateFilterConditionList));
|
||||
}
|
||||
|
||||
/// The [SelectOptionFilterList] must show up first.
|
||||
Future<void> tapOptionFilterWithName(String name) async {
|
||||
final findCell = find.descendant(
|
||||
@@ -1194,7 +1218,36 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
Future<void> tapDateFilterCondition(DateTimeFilterCondition condition) async {
|
||||
Future<void> changeTextFilterCondition(
|
||||
TextFilterConditionPB condition,
|
||||
) async {
|
||||
await tapButton(find.byType(TextFilterConditionList));
|
||||
final button = find.descendant(
|
||||
of: find.byType(HoverButton),
|
||||
matching: find.text(
|
||||
condition.filterName,
|
||||
),
|
||||
);
|
||||
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
Future<void> changeSelectFilterCondition(
|
||||
SelectOptionFilterConditionPB condition,
|
||||
) async {
|
||||
await tapButton(find.byType(SelectOptionFilterConditionList));
|
||||
final button = find.descendant(
|
||||
of: find.byType(HoverButton),
|
||||
matching: find.text(condition.i18n),
|
||||
);
|
||||
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
Future<void> changeDateFilterCondition(
|
||||
DateTimeFilterCondition condition,
|
||||
) async {
|
||||
await tapButton(find.byType(DateFilterConditionList));
|
||||
final button = find.descendant(
|
||||
of: find.byType(HoverButton),
|
||||
matching: find.text(condition.filterName),
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option/option_actions.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_title.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
|
||||
@@ -17,10 +18,14 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/uplo
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
@@ -70,6 +75,20 @@ class EditorOperations {
|
||||
expect(find.byType(FlowyEmojiPicker), findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> paste() async {
|
||||
if (UniversalPlatform.isMacOS) {
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
} else {
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> tapGettingStartedIcon() async {
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
@@ -178,6 +197,11 @@ class EditorOperations {
|
||||
await tester.ime.insertCharacter('@');
|
||||
}
|
||||
|
||||
/// trigger the plus action menu (+) command
|
||||
Future<void> showPlusMenu() async {
|
||||
await tester.ime.insertCharacter('+');
|
||||
}
|
||||
|
||||
/// Tap the slash menu item with [name]
|
||||
///
|
||||
/// Must call [showSlashMenu] first.
|
||||
@@ -274,10 +298,42 @@ class EditorOperations {
|
||||
widget.blockComponentContext.node.path.equals(path),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(PopoverActionList));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// open the turn into menu
|
||||
Future<void> openTurnIntoMenu(Path path) async {
|
||||
await hoverAndClickOptionMenuButton(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_turnInto.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(TurnIntoOptionMenu));
|
||||
}
|
||||
|
||||
/// copy link to block
|
||||
Future<void> copyLinkToBlock(Path path) async {
|
||||
await hoverAndClickOptionMenuButton(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> openDepthMenu(Path path) async {
|
||||
await hoverAndClickOptionMenuButton(path);
|
||||
await tester.tapButton(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_depth.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpUntilFound(find.byType(DepthOptionMenu));
|
||||
}
|
||||
|
||||
/// Drag block
|
||||
///
|
||||
/// [offset] is the offset to move the block.
|
||||
@@ -329,9 +385,13 @@ class EditorOperations {
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
}
|
||||
|
||||
Finder findDocumentTitle(String title) {
|
||||
Finder findDocumentTitle(String? title) {
|
||||
final parent = UniversalPlatform.isDesktop
|
||||
? find.byType(CoverTitle)
|
||||
: find.byType(DocumentImmersiveCover);
|
||||
|
||||
return find.descendant(
|
||||
of: find.byType(CoverTitle),
|
||||
of: parent,
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) {
|
||||
if (widget is! TextField) {
|
||||
@@ -342,6 +402,10 @@ class EditorOperations {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (title.isEmpty) {
|
||||
return widget.controller?.text.isEmpty ?? false;
|
||||
}
|
||||
@@ -351,4 +415,23 @@ class EditorOperations {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// open the more action menu on mobile
|
||||
Future<void> openMoreActionMenuOnMobile() async {
|
||||
final moreActionButton = find.byType(MobileViewPageMoreButton);
|
||||
await tester.tapButton(moreActionButton);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// click the more action item on mobile
|
||||
///
|
||||
/// rename, add collaborator, publish, delete, etc.
|
||||
Future<void> clickMoreActionItemOnMobile(String name) async {
|
||||
final moreActionItem = find.descendant(
|
||||
of: find.byType(MobileQuickActionButton),
|
||||
matching: find.findTextInFlowyText(name),
|
||||
);
|
||||
await tester.tapButton(moreActionItem);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,15 @@ 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 '../desktop/board/board_hide_groups_test.dart';
|
||||
import 'base.dart';
|
||||
import 'common_operations.dart';
|
||||
|
||||
@@ -117,4 +118,20 @@ 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();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/startup/tasks/device_info_task.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class CocoaWindowChannel {
|
||||
CocoaWindowChannel._();
|
||||
@@ -44,7 +44,14 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!Platform.isMacOS) {
|
||||
// the frameless window is only supported on macOS
|
||||
if (!UniversalPlatform.isMacOS) {
|
||||
return widget.child ?? const SizedBox.shrink();
|
||||
}
|
||||
|
||||
// For the macOS version 15 or higher, we can control the window position by using system APIs
|
||||
if (ApplicationInfo.macOSMajorVersion != null &&
|
||||
ApplicationInfo.macOSMajorVersion! >= 15) {
|
||||
return widget.child ?? const SizedBox.shrink();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class NetworkListener {
|
||||
|
||||
@@ -3,7 +3,9 @@ import 'package:scaled_app/scaled_app.dart';
|
||||
import 'startup/startup.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
ScaledWidgetsFlutterBinding.ensureInitialized(scaleFactor: (_) => 1.0);
|
||||
ScaledWidgetsFlutterBinding.ensureInitialized(
|
||||
scaleFactor: (_) => 1.0,
|
||||
);
|
||||
|
||||
await runAppFlowy();
|
||||
}
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -32,6 +33,9 @@ extension MobileRouter on BuildContext {
|
||||
if (fixedTitle != null) {
|
||||
queryParameters[MobileDocumentScreen.viewFixedTitle] = fixedTitle;
|
||||
}
|
||||
if (blockId != null) {
|
||||
queryParameters[MobileDocumentScreen.viewBlockId] = blockId;
|
||||
}
|
||||
}
|
||||
|
||||
final uri = Uri(
|
||||
|
||||
+4
-2
@@ -56,11 +56,12 @@ class NotificationReminderBloc
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
pageTitle: view.nameOrDefault,
|
||||
view: view,
|
||||
reminderContent: node.delta?.toPlainText() ?? '',
|
||||
nodes: [node],
|
||||
status: NotificationReminderStatus.loaded,
|
||||
blockId: reminder.meta[ReminderMetaKeys.blockId],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -68,7 +69,7 @@ class NotificationReminderBloc
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
pageTitle: view.nameOrDefault,
|
||||
view: view,
|
||||
reminderContent: reminder.message,
|
||||
status: NotificationReminderStatus.loaded,
|
||||
@@ -205,6 +206,7 @@ class NotificationReminderState with _$NotificationReminderState {
|
||||
@Default(NotificationReminderStatus.initial)
|
||||
NotificationReminderStatus status,
|
||||
@Default([]) List<Node> nodes,
|
||||
String? blockId,
|
||||
ViewPB? view,
|
||||
}) = _NotificationReminderState;
|
||||
|
||||
|
||||
+11
@@ -441,4 +441,15 @@ class PageStyleCover {
|
||||
bool get isCustomImage => type == PageStyleCoverImageType.customImage;
|
||||
bool get isUnsplashImage => type == PageStyleCoverImageType.unsplashImage;
|
||||
bool get isLocalImage => type == PageStyleCoverImageType.localImage;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! PageStyleCover) {
|
||||
return false;
|
||||
}
|
||||
return type == other.type && value == other.value;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(type, value);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/document_collaborators.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
@@ -29,6 +30,7 @@ class MobileViewPage extends StatefulWidget {
|
||||
this.arguments,
|
||||
this.fixedTitle,
|
||||
this.showMoreButton = true,
|
||||
this.blockId,
|
||||
});
|
||||
|
||||
/// view id
|
||||
@@ -37,6 +39,7 @@ 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;
|
||||
@@ -62,6 +65,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
_appBarOpacity.dispose();
|
||||
_scrollNotificationObserver?.removeListener(_onScrollNotification);
|
||||
_scrollNotificationObserver = null;
|
||||
|
||||
super.dispose();
|
||||
@@ -94,6 +98,10 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
ShareBloc(view: view)..add(const ShareEvent.initial()),
|
||||
),
|
||||
if (view.layout.isDocumentView)
|
||||
BlocProvider(
|
||||
create: (_) => DocumentPageStyleBloc(view: view)
|
||||
@@ -172,6 +180,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
context: PluginContext(userProfile: state.userProfilePB),
|
||||
data: {
|
||||
MobileDocumentScreen.viewFixedTitle: widget.fixedTitle,
|
||||
MobileDocumentScreen.viewBlockId: widget.blockId,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:appflowy/mobile/presentation/base/view_page/more_bottom_sheet.da
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
@@ -98,6 +99,8 @@ class MobileViewPageMoreButton extends StatelessWidget {
|
||||
providers: [
|
||||
BlocProvider.value(value: context.read<ViewBloc>()),
|
||||
BlocProvider.value(value: context.read<FavoriteBloc>()),
|
||||
BlocProvider.value(value: context.read<MobileViewPageBloc>()),
|
||||
BlocProvider.value(value: context.read<ShareBloc>()),
|
||||
],
|
||||
child: MobileViewPageMoreBottomSheet(view: view),
|
||||
),
|
||||
|
||||
+165
-48
@@ -1,68 +1,185 @@
|
||||
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/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/startup/startup.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/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MobileViewPageMoreBottomSheet extends StatelessWidget {
|
||||
const MobileViewPageMoreBottomSheet({super.key, required this.view});
|
||||
|
||||
final ViewPB view;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewPageBottomSheet(
|
||||
view: view,
|
||||
onAction: (action) {
|
||||
switch (action) {
|
||||
case MobileViewBottomSheetBodyAction.duplicate:
|
||||
context.pop();
|
||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||
// show toast
|
||||
break;
|
||||
case MobileViewBottomSheetBodyAction.share:
|
||||
// unimplemented
|
||||
context.pop();
|
||||
break;
|
||||
case MobileViewBottomSheetBodyAction.delete:
|
||||
// pop to home page
|
||||
context
|
||||
..pop()
|
||||
..pop();
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
break;
|
||||
case MobileViewBottomSheetBodyAction.addToFavorites:
|
||||
case MobileViewBottomSheetBodyAction.removeFromFavorites:
|
||||
context.pop();
|
||||
context.read<FavoriteBloc>().add(FavoriteEvent.toggle(view));
|
||||
|
||||
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.rename:
|
||||
// no need to implement, rename is handled by the onRename callback.
|
||||
throw UnimplementedError();
|
||||
return BlocListener<ViewBloc, ViewState>(
|
||||
listener: (context, state) {
|
||||
if (state.successOrFailure.isSuccess && state.isDeleted) {
|
||||
context.go('/home');
|
||||
}
|
||||
},
|
||||
onRename: (name) {
|
||||
if (name != view.name) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(name));
|
||||
}
|
||||
context.pop();
|
||||
},
|
||||
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();
|
||||
}
|
||||
},
|
||||
onRename: (name) {
|
||||
_onRename(context, name);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _publish(BuildContext context) async {
|
||||
final id = context.read<ShareBloc>().view.id;
|
||||
final publishName = await generatePublishName(
|
||||
id,
|
||||
view.name,
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.read<ShareBloc>().add(
|
||||
ShareEvent.publish(
|
||||
'',
|
||||
publishName,
|
||||
[view.id],
|
||||
),
|
||||
);
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.publish_publishSuccessfully.tr(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _unpublish(BuildContext context) {
|
||||
context.read<ShareBloc>().add(const ShareEvent.unPublish());
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.publish_unpublishSuccessfully.tr(),
|
||||
);
|
||||
}
|
||||
|
||||
void _copyPublishLink(BuildContext context) {
|
||||
final url = context.read<ShareBloc>().state.url;
|
||||
if (url.isNotEmpty) {
|
||||
unawaited(
|
||||
getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(plainText: url),
|
||||
),
|
||||
);
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.grid_url_copy.tr(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _visitPublishedSite(BuildContext context) {
|
||||
final url = context.read<ShareBloc>().state.url;
|
||||
if (url.isNotEmpty) {
|
||||
unawaited(
|
||||
afLaunchUrl(
|
||||
Uri.parse(url),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _copyShareLink(BuildContext context) {
|
||||
final workspaceId = context.read<ShareBloc>().state.workspaceId;
|
||||
final viewId = context.read<ShareBloc>().state.viewId;
|
||||
final url = ShareConstants.buildShareUrl(
|
||||
workspaceId: workspaceId,
|
||||
viewId: viewId,
|
||||
);
|
||||
if (url.isNotEmpty) {
|
||||
unawaited(
|
||||
getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(plainText: url),
|
||||
),
|
||||
);
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
|
||||
);
|
||||
} else {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.shareAction_copyLinkToBlockFailed.tr(),
|
||||
type: ToastificationType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onRename(BuildContext context, String name) {
|
||||
if (name != view.name) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+68
-2
@@ -1,21 +1,29 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
enum MobileViewBottomSheetBodyAction {
|
||||
undo,
|
||||
redo,
|
||||
share,
|
||||
rename,
|
||||
duplicate,
|
||||
delete,
|
||||
addToFavorites,
|
||||
removeFromFavorites,
|
||||
helpCenter;
|
||||
helpCenter,
|
||||
publish,
|
||||
unpublish,
|
||||
copyPublishLink,
|
||||
visitSite,
|
||||
copyShareLink,
|
||||
}
|
||||
|
||||
typedef MobileViewBottomSheetBodyActionCallback = void Function(
|
||||
@@ -90,6 +98,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
icon: FlowySvgs.view_item_rename_s,
|
||||
iconSize: const Size.square(18),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.rename,
|
||||
),
|
||||
@@ -100,6 +109,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
: LocaleKeys.button_addToFavorites.tr(),
|
||||
icon: isFavorite ? FlowySvgs.unfavorite_s : FlowySvgs.favorite_s,
|
||||
iconSize: const Size.square(18),
|
||||
onTap: () => onAction(
|
||||
isFavorite
|
||||
? MobileViewBottomSheetBodyAction.removeFromFavorites
|
||||
@@ -110,16 +120,30 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
icon: FlowySvgs.duplicate_s,
|
||||
iconSize: const Size.square(18),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
// copy link
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.shareAction_copyLink.tr(),
|
||||
icon: FlowySvgs.m_copy_link_s,
|
||||
iconSize: const Size.square(18),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.copyShareLink,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
..._buildPublishActions(context),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
icon: FlowySvgs.trash_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
iconSize: const Size.square(18),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.delete,
|
||||
),
|
||||
@@ -129,6 +153,48 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildPublishActions(BuildContext context) {
|
||||
final userProfile = context.read<MobileViewPageBloc>().state.userProfilePB;
|
||||
// the publish feature is only available for AppFlowy Cloud
|
||||
if (userProfile == null ||
|
||||
userProfile.authenticator != AuthenticatorPB.AppFlowyCloud) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final isPublished = context.watch<ShareBloc>().state.isPublished;
|
||||
if (isPublished) {
|
||||
return [
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.shareAction_visitSite.tr(),
|
||||
icon: FlowySvgs.m_visit_site_s,
|
||||
iconSize: const Size.square(18),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.visitSite,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.shareAction_unPublish.tr(),
|
||||
icon: FlowySvgs.m_unpublish_s,
|
||||
iconSize: const Size.square(18),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.unpublish,
|
||||
),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.shareAction_publish.tr(),
|
||||
icon: FlowySvgs.m_publish_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.publish,
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Widget _divider() => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -93,7 +94,7 @@ enum MobilePaneActionType {
|
||||
Navigator.of(sheetContext).pop();
|
||||
viewBloc.add(
|
||||
ViewEvent.createView(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout.defaultName,
|
||||
layout,
|
||||
section: spaceType!.toViewSectionPB,
|
||||
),
|
||||
|
||||
+1
@@ -143,6 +143,7 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
) ??
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: child,
|
||||
|
||||
+8
@@ -11,12 +11,16 @@ class OptionTextField extends StatelessWidget {
|
||||
const OptionTextField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.autoFocus = false,
|
||||
required this.isPrimary,
|
||||
required this.fieldType,
|
||||
required this.onTextChanged,
|
||||
required this.onFieldTypeChanged,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final bool autoFocus;
|
||||
final bool isPrimary;
|
||||
final FieldType fieldType;
|
||||
final void Function(String value) onTextChanged;
|
||||
final void Function(FieldType value) onFieldTypeChanged;
|
||||
@@ -25,10 +29,14 @@ 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(
|
||||
onTap: () async {
|
||||
if (isPrimary) {
|
||||
return;
|
||||
}
|
||||
final fieldType = await showFieldTypeGridBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.grid_field_editProperty.tr(),
|
||||
|
||||
+49
-69
@@ -5,9 +5,8 @@ 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_appflowy_date_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/mobile_date_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/mobile_date_header.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -71,71 +70,52 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDatePicker() => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<DateCellEditorBloc>(
|
||||
create: (_) => DateCellEditorBloc(
|
||||
reminderBloc: getIt<ReminderBloc>(),
|
||||
cellController: widget.controller,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<DateCellEditorBloc, DateCellEditorState>(
|
||||
builder: (context, state) {
|
||||
return MobileAppFlowyDatePicker(
|
||||
selectedDay: state.dateTime,
|
||||
dateStr: state.dateStr,
|
||||
endDateStr: state.endDateStr,
|
||||
timeStr: state.timeStr,
|
||||
endTimeStr: state.endTimeStr,
|
||||
startDay: state.startDay,
|
||||
endDay: state.endDay,
|
||||
enableRanges: true,
|
||||
isRange: state.isRange,
|
||||
includeTime: state.includeTime,
|
||||
use24hFormat: state.dateTypeOptionPB.timeFormat ==
|
||||
TimeFormatPB.TwentyFourHour,
|
||||
timeFormat: state.dateTypeOptionPB.timeFormat,
|
||||
selectedReminderOption: state.reminderOption,
|
||||
onStartTimeChanged: (String? time) {
|
||||
if (time != null) {
|
||||
context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.setTime(time));
|
||||
}
|
||||
},
|
||||
onEndTimeChanged: (String? time) {
|
||||
if (time != null) {
|
||||
context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.setEndTime(time));
|
||||
}
|
||||
},
|
||||
onDaySelected: (selectedDay, focusedDay) => context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.selectDay(selectedDay)),
|
||||
onRangeSelected: (start, end, focused) => context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.selectDateRange(start, end)),
|
||||
onRangeChanged: (value) => context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.setIsRange(value)),
|
||||
onIncludeTimeChanged: (value) => context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.setIncludeTime(value)),
|
||||
onClearDate: () => context
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(const DateCellEditorEvent.clearDate()),
|
||||
onReminderSelected: (option) =>
|
||||
context.read<DateCellEditorBloc>().add(
|
||||
DateCellEditorEvent.setReminderOption(
|
||||
option: option,
|
||||
selectedDay:
|
||||
state.dateTime == null ? DateTime.now() : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
Widget _buildDatePicker() {
|
||||
return BlocProvider(
|
||||
create: (_) => DateCellEditorBloc(
|
||||
reminderBloc: getIt<ReminderBloc>(),
|
||||
cellController: widget.controller,
|
||||
),
|
||||
child: BlocBuilder<DateCellEditorBloc, DateCellEditorState>(
|
||||
builder: (context, state) {
|
||||
final dateCellBloc = context.read<DateCellEditorBloc>();
|
||||
return MobileAppFlowyDatePicker(
|
||||
dateTime: state.dateTime,
|
||||
endDateTime: state.endDateTime,
|
||||
isRange: state.isRange,
|
||||
includeTime: state.includeTime,
|
||||
dateFormat: state.dateTypeOptionPB.dateFormat,
|
||||
timeFormat: state.dateTypeOptionPB.timeFormat,
|
||||
reminderOption: state.reminderOption,
|
||||
onDaySelected: (selectedDay) {
|
||||
dateCellBloc.add(DateCellEditorEvent.updateDateTime(selectedDay));
|
||||
},
|
||||
onRangeSelected: (start, end) {
|
||||
dateCellBloc.add(DateCellEditorEvent.updateDateRange(start, end));
|
||||
},
|
||||
onIsRangeChanged: (value, dateTime, endDateTime) {
|
||||
dateCellBloc.add(
|
||||
DateCellEditorEvent.setIsRange(value, dateTime, endDateTime),
|
||||
);
|
||||
},
|
||||
onIncludeTimeChanged: (value, dateTime, endDateTime) {
|
||||
dateCellBloc.add(
|
||||
DateCellEditorEvent.setIncludeTime(
|
||||
value,
|
||||
dateTime,
|
||||
endDateTime,
|
||||
),
|
||||
);
|
||||
},
|
||||
onClearDate: () {
|
||||
dateCellBloc.add(const DateCellEditorEvent.clearDate());
|
||||
},
|
||||
onReminderSelected: (option) {
|
||||
dateCellBloc.add(DateCellEditorEvent.setReminderOption(option));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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.LastEditedTime,
|
||||
FieldType.CreatedTime,
|
||||
FieldType.Media,
|
||||
FieldType.URL,
|
||||
FieldType.Checkbox,
|
||||
FieldType.Checklist,
|
||||
FieldType.Media,
|
||||
FieldType.LastEditedTime,
|
||||
FieldType.CreatedTime,
|
||||
// FieldType.Time,
|
||||
];
|
||||
|
||||
|
||||
+2
@@ -224,7 +224,9 @@ class _MobileFieldEditorState extends State<MobileFieldEditor> {
|
||||
const _Divider(),
|
||||
OptionTextField(
|
||||
controller: controller,
|
||||
autoFocus: widget.mode == FieldOptionMode.add,
|
||||
fieldType: values.type,
|
||||
isPrimary: widget.isPrimary,
|
||||
onTextChanged: (value) {
|
||||
isFieldNameChanged = true;
|
||||
_updateOptionValues(name: value);
|
||||
|
||||
Loaded 100 of 1031 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user